Generic provider connection
Introduction
Generic Provider Authentication Connection is a plug-in that allows clients to create their own input view, error view, and the handler to validate credentials. Safewhere Identify sends a set of key-value pairs collected from the views as well as some other connection settings to the handler, receives the result, and processes it accordingly.
The configuration settings offered by this Authentication Connection type are:
Second-factor Authentication Connection: If you want this WS-Federation Authentication Connection to have a second factor, you must choose this second factor among the different Authentication Connections that have been set up in the system. This includes all the One-Time Password Connections.
Two-factor identities condition: When using two different Authentication Connections together (which is basically what you are doing when setting up two-factor authentication), the two might try to Safewhere Identify the incoming user based on two different identity bearing claims. This drop-down is activated when a user has chosen that the connection will have a second factor. Options in the drop-down are:
- Use the first identity: The system disregards the "identity bearing claim" value of the second factor and just focuses on identifying the user based on the first one.
- Two identities must be the same: The user is not allowed to log in unless the identity of the user for the first factor is identical to that of the second factor.
Use as second factor only: If you just want the Authentication Connection to be used as the second factor for other connections and not have it offered to users as a primary connection option, this check box must be enabled.
Ignored by second factor roles claim type: If there are subsets of users that you will allow to log in without also having to authenticate using the second factor, you must specify whom these users are based on a rule. The rule states that any users who have a specific value for a specific claim type will be excluded from the second factor. This setting specifies which claim will be tested. The setting below ("Ignored by second factor roles") states which roles will be ignored. Safewhere Identify will search in both the received assertion and the local store.
Ignored by second factor roles: This setting specifies the list of roles (claims type values) that a user must have at least one of in order to avoid having to authenticate via the second factor. You should use colons as separators for these roles.
Ignore roles check: If you do not want to let anyone log in without also authenticating via the second factor (in effect ignoring the preceding two parameters), you should enable this check box.
Federated session lifetime (minutes): This setting specifies how long a federated session, which is established when a user uses this Authentication Connection to log in, will last.
Enabled for mobile use: This setting should be enabled if you also want to allow mobile users to authenticate using this connection.
IP-based filter for Home Realm Discovery: This setting specifies IP addresses of RPs for the filter. An IP address consists of four sections of numbers between 1 and 255. The four sections of numbers are separated by periods. An IP range consists of two IP addresses separated by a dash. You can enter multiple IP addresses or IP ranges by separating them with semicolons, for example, 192.168.1.1;192.168.1.2;192.168.0.0-192.168.1.255.
Credentials validator: This drop-down provides a list of external DLLs or scripting option. Choose the one that should be used to validate credentials. Safewhere Identify then sends credentials collected from the "Login View" as well as "Key-value pairs" (as specified in the "Key-value pairs to pass the external provider" setting) to this DLL or the script.
Script name: When the credentials validator is set to
Scripting generic provider
, the Script name must be specified to indicate which script will perform the validation. Note that all necessary scripts must first be created in the Script library.Login view name: This setting specifies the name of the view that Safewhere Identify shows to the user to collect credentials.
Error view name: This setting specifies the name of the view that Safewhere Identify shows to the user when an error/exception happens or when the maximum allowed attempts are exceeded.
Error view URL: Similar to the above, but this time it is a URL. Either Error view name or Error view URL must have a value. If both have values, the Error view name takes precedence.
Key-value pairs to pass to the external provider: This setting specifies sets of static key-value pairs that Safewhere Identify passes to the external provider every time it calls the method to validate a user's credentials. Multiple pairs can be added.
There is a whole range of fields that will help you set up a two-factor authentication process, which are explained in the above list:
- Use as second factor only.
- Ignored by second factor roles claim type.
- Ignored by second factor roles.
- Ignore roles check.
Case Example for using Generic Provider
DummyGenericProvider is a demo generic provider for external authentication. It validates a user's credentials against a database. First, we need to set up a database for it:
- Create a database named DummyGenericProvider
- Execute the following script to create users for testing:
USE DummyGenericProvider;
GO
CREATE TABLE[dbo].[DummyUser](
[UserName][nchar](10)NOT NULL,
[Password][nchar](10)NOT NULL,
[IsLocked][bit]NOT NULL,
[IsDisabled][bit]NOT NULL,
CONSTRAINT [PK_DummyUser]PRIMARY KEY CLUSTERED
(
[UserName] ASC
))
-- normal user
INSERT INTO[dbo].[DummyUser]([UserName],[Password],[IsLocked],[IsDisabled])
VALUES ('user','dummy', 0, 0);
-- disabled user
INSERT INTO[dbo].[DummyUser]([UserName],[Password],[IsLocked],[IsDisabled])
VALUES ('duser','dummy', 0, 1);
-- locked user
INSERT INTO[dbo].[DummyUser]([UserName],[Password],[IsLocked],[IsDisabled])
VALUES ('luser','dummy', 1, 0);
It is assumed that Safewhere Identify was installed in the default folder and that the tenant name is identify1. Use the Identify Admin connections page to create a new Generic Provider Authentication Connection with the following settings:
Credentials validator: select the ExternalSamples from the dropdown list, named Safewhere.External.Samples.DummyGenericValidatorUsernamePassword, Safewhere.External.Samples
Login view name: Set the value to DummyGenericUsernamePasswordLogin.
Error view name: Set the value to DummyGenericUsernamePasswordError.
Error view url: Leave it empty or set the value to redirected URL if Error view name is empty.
Add key/value to allow the ExternalSamples handler to connect to the user's table created above:
- Key = "ConnectionString" (case sensitive)
- Value = "Server=localhost; Database=DummyGenericProvider; Trusted_Connection=True; MultipleActiveResultSets=true;" (If using local SQL Server, use localhost; otherwise, change to SQL Server Name)
Clients are also able to allow users to return to the Selector page to select another Authentication Connection when at the login page by adding the section @Html.Partial("ReturnToLoginSelectorLink") to their login view. The Generic Provider Login page using ExternalSamples will look as follows:
Technical Information for Implementing External Handlers
The following chapter explains the technical requirements for implementing the external handlers.
General Requirements
Implementing a custom generic provider for external authentication requires the following steps:
- Implement the IGenericCredentialsProvider.
- Create a login view and an error view.
- Add text resources if needed.
The interfaces
[Flags]
public enum CredentialsValidationResultCode
{
None = 0,
Success = 1 << 0,
UserDisabled = 1 << 1,
UserLocked = 1 << 2,
UnknownUserName = 1 << 3,
IncorrectPassword = 1 << 4,
UnknownError = 1 << 5,
MissingRequiredFields = 1 << 6,
// Do we need a login failed flag? Or any value other than Success indicates a failed attempt.
}
public interface IGenericCredentialsValidator
{
///<summary>
///Validates a user and returns validation result
///</summary>
///<param name="input">Including configuration and user input</param>
///<param name="logWriter"></param>
///<returns></returns>
CredentialsValidationResult Validate(IDictionary<string, string> input, IIdentifyLogWriter logWriter);
}
public class CredentialsValidationResult
{
public CredentialsValidationResult()
{
ExternalErrorMessages = newList<string>();
}
public string UserIdentity { get; set; }
public CredentialsValidationResultCode ResultCode { get; set; }
public IList<string>ExternalErrorMessages { get; privateset; }
}
The Result Code Enumeration
- Define all the possible results of a login.
- A result can contain more than one state; for example, it can be UserDisabled | UserLocked.
- The Success state cannot be combined with any other states.
The Validator Interface
The interface has a method called Validate, which receives all the necessary parameters and returns a result object.
Parameters:
- input: A dictionary of key-value pairs. It contains all the static key-value settings of a connection and all the input values collected from the login view. Please note that the keys are not case sensitive.
- logWriter: A logging object that the external implementation can use to log to Safewhere Identify's event log.
The Result Model Class
The result object contains three properties:
- UserIdentity: Of all the key-value pairs that Safewhere Identify passes to an external provider, there should always be one that is the user's identity. It can, for example, be a username, email, or CPR. The external provider is responsible for setting that value to the UserIdentity property so that Safewhere Identify can use it to build the identity-bearing claim.
- ResultCode: An instance of the result code enumeration mentioned above.
- ExternalErrorMessages: A list of error messages that the external implementation wants to pass to Safewhere Identify.
View's Model
- The object containing the data that Safewhere Identify wants to pass to the login view.
- The idea here is that Safewhere Identify tries to pass as much information as possible to the custom login view. The view itself decides what and how the error messages are shown.
public class GenericProviderAuthenticationModel
{
public GenericProviderAuthenticationModel()
{
ErrorMessages = newList<string>();
ExternalErrorMessages = newList<string>();
}
public bool UserDisabled { get; set; }
public bool UserLocked { get; set; }
public bool UnknownUserName { get; set; }
public bool IncorrectPassword { get; set; }
public bool UnknownError { get; set; }
public bool MissingRequiredFields { get; set; }
public IList<string>ErrorMessages { get; privateset; }
public IList<string>ExternalErrorMessages { get; privateset; }
}
Processing flow
The processing flow is:
A generic provider connection is selected.
A custom login view is shown to a user.
The user enters all the required fields and submits.
Safewhere Identify collects all the entered values whose keys are names of the controls, combines them with all the static key-value pairs, and passes them to the configured external handler.
The external handler returns a result object to Safewhere Identify.
Safewhere Identify checks the result object:
If the result code is Success, it authenticates the user.
If the result code is a combination of Success and the code "invalid state," it redirects to the error view.
If the result code is not Success:
- For each invalid state it contains, look up the corresponding error message and add it to the ErrorMessages list.
- Copy the ExternalErrorMessages list from the result object to the model object.
Initialize all the other properties of the model object.
- Show the login view.
- The login view will decide what and how error messages will be shown to the user.
Localization
The plug-in follows the standard localization design used by other plug-ins.
The external DLL needs its own text resources, which are also used for the custom views.
Safewhere Identify's text resource framework is designed to allow changing the default language. Additionally, it is possible to override default texts with user-specific ones. However, these features require specific code implementation. Since external DLLs are typically intended for specific organizations that already know their desired default language and have complete control over the text resource files, a simpler text resource model is used for external DLLs:
Add some resource files to the project.
Text resource files' properties must be set correctly:
- Build Action: None
- Copy to output: Do not copy
- Custom tool: GlobalResourceProxyGenerator
Upon deploying the DLL, also copy the resource files to appropriate folders.
It is recommended that when logging messages to the event log, the external DLL should hard-code messages in the language of its choice instead of reading from text resources. Otherwise, messages may be logged in the user's preferred language.
Event ids
Event IDs that are used by Safewhere Identify for the new plug-in:
- 4810: GenericProviderCommonError.
- 4811: GenericProviderCouldNotLoadExternalProviderError.
- 4812: GenericProviderUnhandledExceptionFromExternalProviderError.
- 4813: GenericProviderNameClaimIsEmptyOrCanNotParsed.
External providers are encouraged to use event IDs between 4830 and 4899 for their errors.