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 new web\UserManager folder in your app
  • Copy the web\views\_Login.wcs file into your web\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 will only display 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. Static pages or pages rendered in code won't show the signin widget unless you explicitly embed it. it's possible to render the widget html with:

lcHtml = Response.ExpandScriptToString("~/usermanager/LoginMenu_Partial.usm")

You can then manually embed this HTML as needed into the Bootstrap menu. Note 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.

Code

  • Copy deploy\UserSecurityManager folder to your projects deploy\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::OnLoad() method add the following:

PROTECTED FUNCTION OnLoad

*** Hook in UserSecurityManager
SET PATH TO ".\UserSecurityManager" ADDITIVE
DO AppUserSecurity 
ADDPROPERTY(this.oConfig,;
            "oUserSecurityManagerProcess",;
            CREATEOBJECT("UserSecurityManagerProcessConfig"))


* ... additional code here

ENDFUNC

This loads two configuration classes:

  • AppUserSecurity UserSecurity sub-class
  • UserSecurityManagerProcessConfig configuration class
  • Adds the User Security class to the Configuration

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 add the scriptmap manually to web\web.config:

<configuration
	...
	<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>	

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:

*************************************************************
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 

This file also contains the UserSecurityManagerProcessConfig which contains some operational parameters such as a company name and sender email address for sending validation and recovery emails. Set up both these classes.

*************************************************************
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"

ENDDEFINE

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, 2-force login
IF 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
	llIgnoreLoginRequest =  INLIST(lcScriptName,;
	  "default")
	 IF !THIS.Authenticate("any","",llIgnoreLoginRequest)
	   IF !llIgnoreLoginRequest
	     RETURN .F.
	   ENDIF
	ENDIF
ENDIF

* ... additional code here

ENDFUNC

Note that if you don't force authentication on startup you can explicitly force authentication for specific requests only:

FUNCTION PriviligedContentPage()

IF !AUTHENTICATE("ANY")
   RETURN   && 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

User Authentication Status

You can always use the various Process authentication properties to check for authentication status and get access to the User object.

Use Process.lIsAuthenticate, Process.cAuthenticatedUser, Process.cAuthenticatedUserName to check for user login status. For more info on user information you can check Process.oUser which gives you access to a logged in user's record. If not logged in this object is available but all values are empty.

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.

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!

And that's all it should take at this point.

Ready

That's it - you should now be able to handle authentication in your Web Connection application.


© West Wind Technologies, 2018 • Updated: 11/04/18
Comment or report problem with topic