Skip to main content

Forms authentication and login management

Forms authentication enables login with a username and password. It is configured server-side in OnConfigureAuthentification.

Enable Forms​

Main.cs
protected override void OnConfigureAuthentification(AuthenticationOptions options)
{
// Enable login / password input on the login page.
options.AllowFormAuthentication = true;
}

Forms can be combined with Windows or an external provider. In that case, the login page determines whether to redirect the user to an external provider or display the Forms password field.

Authentication workflow​

When a user attempts to log in, the application can hook into the workflow via Main.cs in the server project.

StepMethodUsage
Development impersonationGetImpersonatedIdentity()Simulate an identity in DEBUG only.
Context configurationOnConfigureAuthenticationContext(AuthenticationContext context)Set AllowUnknownUser, IsEmailLogin, or custom role options.
User resolutionGetIdentityUserAsync(AuthenticationContext context)Retrieve the user from the context. Override only for special cases.
Unknown userOnUnknownUserAsync(AuthenticationContext context)Create or reject a user missing from the database.
Post-loginOnSignInAsync(AuthenticationContext context, ApplicationUser user)Update the user, its roles, its claims, or the role choices.
Forms and unknown users

By default, the Demo sample does not trigger automatic creation for Forms: AllowUnknownUser is set to false when AuthenticationMode is Forms.

Configure the authentication context​

Main.cs
protected override void OnConfigureAuthenticationContext(AuthenticationContext context)
{
// Unknown users are only accepted outside of Forms.
context.AllowUnknownUser = context.IdentitySource.AuthenticationMode != AuthenticationMode.Forms;

// User lookup is done by UserName rather than Email.
context.IsEmailLogin = false;
}

The main context properties are:

PropertyDescription
IdentitySource.AuthenticationModeLogin mode: Forms, Windows, or SSO.
IdentitySource.ClaimsClaims provided by the identity provider, notably in SSO.
IdentitySource.SourceNameName of the identity source.
IdentitySource.UserLoginLogin provided by the identity source.
AllowUnknownUserIndicates whether OnUnknownUserAsync can be called.
IsEmailLoginIndicates whether the user is looked up by Email rather than by UserName.
IsCustomRolesEnabledIndicates whether roles should be replaced by the context's custom roles.

Impersonation in development​

GetImpersonatedIdentity() is only used for testing and development. The method must remain guarded by #if DEBUG.

Main.cs
#if DEBUG
public override IdentitySource GetImpersonatedIdentity()
{
// Read the simulated identity from the development configuration.
return ImpersonatedIdentity.FromConfiguration(Configuration);
}
#endif
appsettings.Development.json
{
"Impersonate": {
"Enabled": true,
"Login": "admin",
"FirstName": "Admin",
"LastName": "Demo"
}
}

Handle an unknown user​

OnUnknownUserAsync is called if no user is found and AllowUnknownUser permits it. It can return a user created by the application, or null to reject the login.

Main.cs
protected override Task<ApplicationUser> OnUnknownUserAsync(AuthenticationContext context)
{
// The application refuses automatic user creation.
return Task.FromResult<ApplicationUser>(null);
}

Example of creation on login for a non-Forms identity:

Main.cs
protected override Task<ApplicationUser> OnUnknownUserAsync(AuthenticationContext context)
{
string userLogin = context.IdentitySource.UserLogin;

ApplicationUser user = new()
{
Email = userLogin,
UserName = userLogin,
EmailConfirmed = true
};

// Create the user with an application role.
return CreateUserAsync(user, Roles.User);
}

Handle post-login with OnSignInAsync​

OnSignInAsync is called when the user is known. It notably allows updating roles derived from an external factor or adding claims to the token with context.AddCustomClaim(...). A claim used by a server policy must be declared in AuthenticationOptions.AccessTokenClaims: see Policy based on a custom claim.

Main.cs
protected override Task OnSignInAsync(AuthenticationContext context, ApplicationUser user)
{
// External roles replace the existing roles during login.
context.IsCustomRolesEnabled = true;
context.AddCustomRoles(GetExternalRoles(context.IdentitySource.UserLogin));

return Task.CompletedTask;
}

private static IEnumerable<string> GetExternalRoles(string userLogin)
{
// Educational example: replace with the application's actual external source.
return new[] { Roles.User.ToString(), Roles.Admin.ToString() };
}
Custom roles

AddCustomRole and AddCustomRoles require enabling IsCustomRolesEnabled before adding. If no role is available, the login fails.

Role or profile choice​

If the user has several roles, the application can offer a choice during the workflow.

Main.cs
protected override Task OnSignInAsync(AuthenticationContext context, ApplicationUser user)
{
// Offer the available roles to the logged-in user.
context.EnableGroupChoice<Roles>();

return Task.CompletedTask;
}

It is also possible to explicitly add choices grouping roles.

Main.cs
protected override Task OnSignInAsync(AuthenticationContext context, ApplicationUser user)
{
// Offer profiles composed of application roles.
context.AddRoleChoice("Admin & User", Roles.Admin.ToString(), Roles.User.ToString());
context.AddRoleChoice("Only Admin", Roles.Admin.ToString());
context.AddRoleChoice("Only User", Roles.User.ToString());

return Task.CompletedTask;
}

Retrieve the user​

By default, Lagoon retrieves the user with FindByEmailAsync if IsEmailLogin is true, otherwise with FindByNameAsync. Only override GetIdentityUserAsync for a special case, for example several accounts for the same user with an active/inactive concept.