Manual Project Configuration
The manual process involves quite a few steps to copy files and hook in the process class and UI components. Most of these steps are simple, but there are quite a few of them so it's important to follow instructions carefully.
For demonstration purposes I'll use SecurityTest and SecurityProcess as the project and process class names of the project to integrate into
Copy Files and Folders
First we need to copy files both to the Web and Deploy (code) folders. The SecurityManager is fairly isolated so copying files is pretty straight forward.
Web
Copy files from the UserSecurityManager project folder to your Web Connection Project folder:
- Copy the
web\UserManager
folder int a newweb\UserManager
folder in your app - Copy the
web\views\_Login.wcs
file into yourweb\views
folder
Note the _login.wcs
file is very important as it ensures that you see the additional login features in the login form and that login operations are also routed to the proper login operation in the User Security Manager process.
Add Web Sign In Menu in views\_layoutpage.wcs
In order to be able to explicitly sign in and out, you'll want to use a menu option on the main menu most likely. We've provided a pre-made partial control that you can use for any script or template pages or more specifically on any page that uses a Master Layout page. The easiest and most common way is to add the partial login control to your layout page.
To add this control to your Layout page, go into your views\_layoutpage.wcs
page and add the following <%= RenderPartial() %>
link into the menu header:
<nav class="banner-menu-top float-right">
<a href="#" class="hidable">
<i class="fas fa-book"></i>
Link
</a>
<a href="~/">
<i class="fas fa-home"></i>
Home
</a>
<!-- ADD THIS TO SHOW THE Login/Logout Menu -->
<%= RenderPartial("~/UserManager/LoginMenu_Partial.usm") %>
</nav>
Sign in Widget only shows in Content Pages with Layout
Note this example embeds the signin menu on pages that use the
_layoutpage.wcs
as the master layout. Any other script/template pages have to explicitly embed this partial control using the syntax above.For raw code driven pages, you can manually embed the HTML as needed into a Bootstrap menu:
lcHtml = Response.ExpandScriptToString("~/usermanager/LoginMenu_Partial.usm")
This widget is defined as a bootstrap dropdown meant to be dropped into the menu, but you can customize or create a new widget that works for other styled integration points.
Important: Static HTML pages cannot show signin information because authentication validation requires FoxPro code to run.
Code
- Copy
deploy\UserSecurityManager
folder to your projectsdeploy\UserSecurityManager
This folder contains:
- UserSecurityManagerProcess.prg
- AppUserSecurity.prg
- AppUserSecurity.dbf/fpt/cdx
These files are the code and data dependencies required to run the UserSecurity Manager.
The UserSecurityManagerProcess.prg
file is a self-contained wwProcess implementation that provides the stock authentication features provided by this library. This is the process class that is mapped by the .usm
extension.
The AppUserSecurity.prg
becomes your application specific subclass of the wwUserSecurity
class where you can set application specific settings for your user security instance. Specifically you can customize the filename, whether or not encryption is used and whether emails are validated or not.
Add Code to SecurityTestMain.prg
In order for the UserSecurityManagerProcess.prg
to be hooked up to your existing application you need to excplicitly hook up this class and reference the library and dependencies.
Reference AppUserSecurity.prg
In the YourApp::OnInitCompleted()
method add the following:
PROTECTED FUNCTION OnInitCompleted()
*** Hook in UserSecurityManager
SET PATH TO ".\UserSecurityManager" ADDITIVE
DO wwEncryption && Required library for encrypted passwords
*** The following two are dependencies that are added in AppUserSecurity defined below
DO AppUserSecurity && Add custom security class
*** Hook up config class to main server Configuration
ADDPROPERTY(this.oConfig,;
"oUserSecurityManagerProcess",;
CREATEOBJECT("UserSecurityManagerProcessConfig"))
ENDFUNC
Note: The auto-generated code puts the above code into the
OnLoad()
rather thanOnInitCompleted()
. This is due to the fact that the default Web Connection templates don't generateOnInitCompleted()
reliably. The auto-config includes a note that recommends moving the code fromOnLoad()
toOnInitCompleted()
.
This loads two configuration classes:
AppUserSecurity
which is awwUserSecurity
sub-classUserSecurityManagerProcessConfig
configuration class- Adds the User Security class to the Server Configuration
AppUserSecurity.prg
holds both of the classes for simplicity and you can customize these classes as you see fit - Specifically AppUserSecurity can point at other files or file locations or if necessary use a different data source to retrieve user security data.
Add USM Extension to your Process Handling
Add the following extension mapping to your SecurityTestServer::Process()
method in the area where extensions are mapped:
CASE lcExtension == "USM"
DO UserSecurityManagerProcess with THIS
You'll also need a scriptmap for .usm
. You can either manually add it.
Add the USM Scriptmap to your YourApp.ini
In YourApp.ini
add usm
to the ServerConfig::ScriptMaps
setting:
[ServerConfig]
Virtual=SecurityTest
ScriptMaps=wc,wcs,md,st,usm
IISPath=IIS://localhost/w3svc/1/root
If you're using IIS you can then just do (running under Admin)
DO SecurityTest_Config.prg
For IIS Express or if you want to configure manually for IIS, add the scriptmap manually to web\web.config
:
<configuration
<webConnectionConfiguration>
<!-- *** add USM here *** -->
<add key="LiveReloadExtensions" value=".sp,.usm,.wc,.wcs,.html,.htm,.css,.js,.ts" />
</webConnectionConfiguration>
<system.webServer>
<handlers>
<add name=".wcs_wconnect-module" path="*.wcs" verb="*" type="Westwind.WebConnection.WebConnectionHandler,WebConnectionModule" preCondition="integratedMode"/>
...
<!-- *** add USM here *** -->
<add name=".usm_wconnect-module" path="*.usm" verb="*" type="Westwind.WebConnection.WebConnectionHandler,WebConnectionModule" preCondition="integratedMode"/>
</handlers>
</system.webServer>
</configuration>
If you want to use the local Web Connection Web Server (.NET Core Middleware) add the .usm
extension to the webconnectionwebserversettings.xml
:
<?xml version="1.0" encoding="utf-8"?>
<WebConnectionWebServerSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<HandledExtensions>.sp,.usm,.wc,.wcs,.wcsx</HandledExtensions>
...
<LiveReloadExtensions>.sp,.usm,.wc,.wcs,.md,.html,.htm,.css,.js,.ts</LiveReloadExtensions>
</WebConnectionWebServerSettings>
Customize the AppUserSecurity Class
AppUserSecurity.prg
contains a subclass of wwUserSecurity class in which you can customize the behavior of your user security class.
You can specify whether new accounts have to be validated, and whether passwords are encrytped:
SET PROCEDURE TO AppUserSecurity ADDITIVE
*************************************************************
DEFINE CLASS AppUserSecurity AS wwUserSecurity
*************************************************************
#IF .F.
*:Help Documentation
*:Topic:
Class AppUserSecurity
*:Description:
Create a custom User Security class that sets up that
customizes user security. You should rename this class
for each application you create.
Then change the cAuthenticationUserSecurityClass in
UserSecurityManagerProcess
*:ENDHELP
#ENDIF
*** overridden properties
*-- Alias for the user file
calias = "AppUserSecurity"
*-- Filename for the user file.
cfilename = "AppUserSecurity"
*-- If .t. requires Email validation of new accounts
lRequireValidation = .T.
*-- if set encrypts the passwords
cPasswordEncryptionKey = "619fasd34ads"
ENDDEFINE
*EOC AppUserSecurity
*************************************************************
DEFINE CLASS UserSecurityManagerProcessConfig AS wwConfig
*************************************************************
cHTMLPagePath = "c:\WebConnectionProjects\UserSecurityManager\Web\"
cDATAPath = ""
cVirtualPath = "/UserSecurityManager/"
cEmailSenderName = "User Administration Manager"
cEmailSender = "admin@your-domain.com"
cEmailReplyTo = "noreply@your-domain.com"
cCompanyName = "ACME Products"
cCookieName = "_SecurityTest"
*** Determines whether the user can delete his or her own profile
lAllowUserToDeleteProfile = .T.
ENDDEFINE
Note you might be wondering how this class gets hooked up: The UserSecurityManagerProcessConfig class is hooked up in the
SecurityTestServer::OnLoad()
code shown earlier.*** Hook up config class to main server Configuration ADDPROPERTY(this.oConfig,; "oUserSecurityManagerProcess",; CREATEOBJECT("UserSecurityManagerProcessConfig"))
Configure your Process Class
In order for the processing to work we need to override the default Authentication behavior in your process class.
Configuration Settings Properties
First we need to tell the process class to use User Security Authentication and to use the AppUserSecurity class we configured earlier. Set the following properties at the top of the class in the class header:
cAuthenticationMode = "UserSecurity" && `Basic` is default
cAuthenticationUserSecurityClass = "AppUserSecurity"
OnProcessInit()
Next we need to enable session state in your process class and enable authentication. The following example explicitly forces authentication to all requests except the home page and login/logout:
FUNCTION OnProcessInit
LOCAL lcScriptName, llIgnoreLoginRequest
*** Share the cookie with the UserSecurityManager cookie
THIS.InitSession(Server.oConfig.oUserSecurityManagerProcess.cCookieName,3600,.T.)
*** Global Authentication Mode
LOCAL lnLoginMode
lnLoginMode = 0 && 0-no auto authentication, 1 - Authenticate but don't force a login 2- always force a login to unauthenticated requests
DO CASE
CASE lnLoginMode = 1
this.Authenticate("ANY") && apply logged in user but don't force login
CASE lnLoginMode = 2
*** Authenticate each request and force a login
*** to all requests EXCEPT the ones in the list
lcScriptName = LOWER(JUSTFNAME(Request.GetPhysicalPath()))
*** Update this list with any endpoints that
*** SHOULD NOT AUTHENTICATE. Leave out extensions.
llIgnoreLoginRequest = INLIST(lcScriptName,;
"default")
IF !THIS.Authenticate("any","",llIgnoreLoginRequest)
IF !llIgnoreLoginRequest
RETURN .F.
ENDIF
ENDIF
ENDCASE
* ... additional code here
ENDFUNC
Authenticate Per Page
If you use lnLoginMode = 2
above every page except those explicitly excluded will force authentication.
If you want to selectively authenticate specific pages you can use THIS.Authenticate()
in a process method and RETURN .F.
if not authenticated:
FUNCTION PriviligedContentPage()
IF !THIS.Authenticate("ANY")
RETURN .F. && Forces Authentication dialog if not logged in
ENDIF
*** this.oUser lets you check for a specific user or level
*** or whatever else you hook up for validation on that object
IF (this.oUser.Level < 5)
this.ErrorMsg("Access Denied","You don't have the necessary rights to access this page.")
RETURN
ENDIF
* ... secret sauce
Response.ExpandScript()
ENDFUNC
If THIS.Authenticate("ANY")
fails to authenticate the user, a 401 request will be returned and the user is automatically redirected to the Login.usm
page.
This allows for more control over the process, but it's more explicit and requires diligence to lock down each and every request that is restricted.
User Authentication Status
Once a user is logged in you can use various
Process
authentication properties to check for authentication status and get access to the User object.Use
Process.lIsAuthenticated
,Process.cAuthenticatedUser
,Process.cAuthenticatedUserName
to check for user login status. For access to the user profile you can checkProcess.oUser
which gives access to the data from the user table. If a user is not logged in, theProcess.oUser
object is available, but all values are empty. Always useProcess.lIsAuthenticated
to check if the user is in fact authenticated before checkingProcess.oUser
.
Login and Logout Links - Use Login.usm/Logout.usm
All login/logout and other UI based authentication operations are essentially deferred to the UserSecurityManagerProcess
class, so no additional code needs to be hooked up in your application class. Other than checking for security and accessing the Process.oUser
to get security information you generally don't need to worry about behavior
Ready, Set, Authenticate
That's it - you should now be able to handle authentication in your Web Connection application.
© West Wind Technologies, 1996-2020 • Updated: 05/08/20
Comment or report problem with topic