Go to content

Bulletcode.NET

Authentication

Bulletcode.NET extends the built-in authentication mechanism in ASP.NET Core. It provides support for cookie and JWT bearer authentication schemes based on local users, and implements a simple API key authentication scheme.

The simplest way to restrict access to a controller or a particular action is by using the Authorize attribute with the AuthenticationSchemes property. The AuthenticationConstants class defines the names of schemes defined by Bulletcode.NET: UserCookieScheme, UserTokenScheme and ApiKeyScheme. The UserSchemes constant includes both cookie and token authentication schemes.

NOTE

When more granular access control is required to authorize permissions for individual actions, you can use the role-based authorization mechanism implemented by Bulletcode.NET. See the Authorization chapter for more information.

Local users

Both cookie and JWT bearer authentication schemes implemented by Bulletcode.NET are based on user entities which are stored in the application’s database and authenticated using a hashed password.

In order to use either of these authentication schemes, the application must define a database context which inherits CoreDbContext, as described in the Database chapter. The built-in User database entity stores the user information, and the IUserService provides methods for retrieving, adding, updating and deleting user entities.

The application can inherit the User class and add custom properties. In this case, it can also override the built-in IUserClaimsFactory<TUser> service in order to store additional user information in the ClaimsPrincipal object which is stored in the cookie or the JWT token.

The default implementation of this service stores the user’s ID, display name (which defaults to the user’s email, unless overridden in the custom User class), email, role and authentication key. The authentication key is a random value which is updated whenever the user’s password is changed, so that all existing cookies and tokens can be invalidated.

Bulletcode.NET implements the following extension methods which extend the ClaimsPrincipal object:

  • FindUserId() — returns the user’s ID as an integer value; this value is stored in the ClaimTypes.NameIdentifier claim.
  • FindRole() — returns the user’s role; this value is stored in the ClaimTypes.Role claim.

The cookie authentication scheme in Bulletcode.NET is based on the built-in support for cookie authentication in ASP.NET Core. Call the AddUserCookie<TUser>() extension method to enable it:

services
    .AddAuthentication( AuthenticationConstants.UserCookieScheme )
    .AddUserCookie<User>();

In the example above, the user cookie scheme is also configured as the default authentication scheme. The built-in User class is passed as a template parameter to the AddUserCookie() method, but if your application implements a custom user entity which inherits User, it should be passed instead.

The UserCookieOptions class contains options for cookie authentication. They can be changed by passing a callback to the AddUserCookie() method, or by adding a UserCookie subsection in the Authentication section of the appsettings.json file.

  • LoginPath — the path of the login page. If not set, the default "/Account/Login" is used.
  • CookieName — the name of the cookie storing user’s identify. The default value is ".BC.User".
  • ExpireTimeSpan — the expiration time of the cookie. The default value is 2 hours.
  • PersistentExpireTimeSpan — the expiration time of the cookie when the “remember me” option is selected. The default value is 14 days.
  • ValidationTimeSpan — the interval at which the validity of the cookie is verified by querying the database. The default value is 5 minutes.
  • TokenExpireTimeSpan — the lifetime of the token which can be used to reset the user’s password. The default value is 8 hours.

Bulletcode.NET uses the ICookieHandler service to implement custom event handlers for the cookie authentication events. The default implementation of this service validates that the user exists in the database and the authentication key matches the one stored in the cookie, and recreates the cookie is the principal is still valid. It also returns an error in JSON format when a REST API call is detected, instead of redirecting to the login page (see the Diagnostics chapter).

NOTE

Both the challenge and the forbid handler for cookie authentication return a 403 status code when a REST API call is detected, because a 401 status code can only be used with the WWW-Authenticate header, and doesn’t make sense for cookie authentication. In order to authorize access to the REST API, the token authentication scheme, described below, should be used instead.

The application should use the IAccountService<TUser> service to manage user cookies and to change or reset user passwords. The TUser parameter should be the built-in User class, or a custom class which inherits it. This service contains the following methods:

  • SignInAsync() — checks if the email and password are valid and generates a user cookie. Returns false if the email or password is invalid.
  • SignOutAsync() — destroys the user cookie.
  • ChangePasswordAsync() — checks if the current password is valid and sets a new password. Returns false if the user is not logged in or the current password is invalid.
  • RequestResetPasswordAsync() — generates a token for resetting the password for the specified user. Returns null if the user doesn’t exist. After calling this method, the application should send an email containing a link with the token.
  • IsPasswordResetTokenValidAsync() — returns true if the specified password reset token is valid.
  • ResetPasswordAsync() — sets a new password for a user associated with the specified password reset token.
  • RefreshSignInAsync() — creates a new cookie for the currently logged in user. This method is automatically called by ChangePasswordAsync() so that the user remains logged in.

Since cookie authentication relies on the built-in mechanism in ASP.NET Core, the user cookies are encrypted using the built-in data protection mechanism.

The IUserClaimsFactory<TUser> service (see above) is used to generate claims stored in the user cookie.

Token authentication

The token authentication scheme in Bulletcode.NET is based on the built-in support for JWT bearer authentication is ASP.NET core. Call the AddUserToken<TUser>() extension method to enable it:

services
    .AddAuthentication( AuthenticationConstants.UserCookieScheme )
    .AddUserCookie<User>()
    .AddUserToken<User>();

The token authentication scheme is typically used with a REST API. The API should implement a method which authenticates a user based on the email and password and returns a token, and a method which can be used to keep the session alive by creating a new token based on the existing one. See the API Client chapter for more information about storing and refreshing the token in client applications.

The UserTokenOptions class contains options for token authentication. They can be changed by passing a callback to the AddUserToken() method, or by adding a UserToken subsection in the Authentication section of the appsettings.json file.

  • SigningKey — the key for signing the token.
  • EncryptingKey — the key for encrypting the token.
  • ExpireTimeSpan — the expiration time of the token. The default value is 1 hour.
  • Realm — the realm name for the WWW-Authenticate header. The default value is the request’s host name.

WARNING

Make sure that you don’t use the same SigningKey and EncryptingKey in your production environment as the default keys used in development environment. Both keys should be random strings containing 32 or more characters.

Bulletcode.NET users the IJwtBearerHandler service to implement custom event handlers for the JWT bearer authentication events. The default implementation sets the WWW-Authenticate header and returns an error in JSON format when a REST API call is detected.

The application should use the ITokenService<TUser> to create and refresh tokens. The TUser parameter should be the built-in User class, or a custom class which inherits it. This service contains the following methods:

  • AuthenticateAsync() checks if the email and password are valid and generates a user token. Returns null if the email or password is invalid.
  • RefreshTokenAsync() creates a new token for the currently logged in user.

The JWT token created by the token authentication scheme does not contain an issuer or audience information. It is validated by using the signing key. The token is also encrypted so that user information cannot be extracted from it.

The IUserClaimsFactory<TUser> service (see above) is used to generate claims stored in the JWT token.

API key authentication

The API key authentication scheme is a simple authentication mechanism using a fixed bearer token.

Call the AddApiKey() extension method to enable API key authentication:

services
    .AddAuthentication()
    .AddApiKey();

The ApiKeyAuthenticationOptions class contains options for API key authentication. They can be changed by passing a callback to the AddApiKey() method, or by adding an ApiKey subsection in the Authentication section of the appsettings.json file:

  • ApiKey - the value of the API key.
  • Realm — the realm name for the WWW-Authenticate header. The default value is the request’s host name.

WARNING

Make sure that you don’t use the same API key in your production environment as the default keys used in development environment.

The API key can be used to protect access for operations which are executed remotely, such as running cron jobs, to guard the application against DoS attacks. However, it should not be used for protecting sensitive information.

Bulletcode.NET uses the ApiKeyAuthenticationHandler class to implement API key authentication. The application can customize the validation, challenge and forbidden event handlers. The default event handlers create an empty claims principal with the AuthenticationConstants.ApiKeyScheme when the bearer token in the request matches the configured API key value, or set the WWW-Authenticate header and return an error in JSON format when a REST API call is detected.