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
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.
Copy files from the UserSecurityManager project folder to your Web Connection Project folder:
- Copy the
web\UserManagerfolder int a new
web\UserManagerfolder in your app
- Copy the
web\views\_Login.wcsfile into your
_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.
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>
Note this example embeds the signin menu on pages that use the
_layoutpage.wcsas 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.
deploy\UserSecurityManagerfolder to your projects
This folder contains:
These files are the code and data dependencies required to run the UserSecurity Manager.
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
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.
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.
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
OnInitCompleted(). This is due to the fact that the default Web Connection templates don't generate
OnInitCompleted()reliably. The auto-config includes a note that recommends moving the code from
This loads two configuration classes:
AppUserSecuritywhich is a
- 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 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.
usm to the
[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)
For IIS Express or if you want to configure manually for IIS, add the scriptmap manually to
<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
<?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>
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 = "email@example.com" cEmailReplyTo = "firstname.lastname@example.org" 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"))
In order for the processing to work we need to override the default Authentication behavior in your process class.
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"
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
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
THIS.Authenticate("ANY") fails to authenticate the user, a 401 request will be returned and the user is automatically redirected to the
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.
Once a user is logged in you can use various
Processauthentication properties to check for authentication status and get access to the User object.
Process.cAuthenticatedUserNameto check for user login status. For access to the user profile you can check
Process.oUserwhich gives access to the data from the user table. If a user is not logged in, the
Process.oUserobject is available, but all values are empty. Always use
Process.lIsAuthenticatedto check if the user is in fact authenticated before checking
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
That's it - you should now be able to handle authentication in your Web Connection application.
Comment or report problem with topic