Skip to content

Web API Security

Authentication

The Domain Services Web APIs are protected using token based authentication. A JWT bearer token must be provided in the Authorization header with each HTTP request:

Authorization: Bearer {token}

If a request is not properly authenticated, the server will return a response with HTTP status code 401 (Unauthorized).

The JWT tokens (also called the access tokens) are retrieved from the api/tokens endpoint. The below example shows such a request:

POST https://localhost:44355/api/tokens
Content-Type: application/json
{
  "id": "john.doe",
  "password": "mysecretpassword"
}

Each access token comes with an associated refresh token. Below is the response body from the above request:

{
  "accessToken": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJBZG1pbiIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6WyJHdWVzdCIsIlVzZXIiLCJFZGl0b3IiLCJBZG1pbmlzdHJhdG9yIl0sImV4cCI6MTU3Mjk4NDc2MSwiaXNzIjoiZGhpZ3JvdXAuY29tIiwiYXVkIjoiZGhpZ3JvdXAuY29tIn0.Qp_3otTxnJSXVkDfbAydZIPPuDRs6KLQuDgMyOMmol4",
    "expiration": "2019-11-05T20:12:41Z"
  },
  "refreshToken": {
    "token": "SioLA77CJRvn1UOC+d6kX3ajDWpoeDqVwYUK3IRxLs8=",
    "expiration": "2020-11-04T19:42:41.9122452Z"
  }
}

To prevent client-side caching of tokens, the response contains the following headers:

cache-control: no-store 
pragma: no-cache 

A refresh token is a one-time-use token to be exchanged for a new access token. The lifetime of the tokens are configurable, but normally the refresh tokens have a longer lifetime than the access tokens. If a refresh token becomes compromised, it can be revoked so when any client application attempts to exchange it for a new access token, the request will be denied.

Access tokens are exchanged using the api/tokens/refresh endpoint. The below example shows such a request:

POST https://localhost:44355/api/tokens/refresh
Content-Type: application/json
"SioLA77CJRvn1UOC+d6kX3ajDWpoeDqVwYUK3IRxLs8="

JWT Signatures

For security reasons, the JWT access tokens are signed with a digital signature. Signing the tokens allows the recipients to validate that the content of the token wasn't changed since it was issued (to avoid man-in-the-middle attacks). It is also used to verify that the original issuer of the token created the signature.

The RS256-algorithm used for signing is an asymmetric algorithm that uses a pair of a private and a public key.

The signing works like this:

  1. Once the user credentials (or refresh token) has been received, the authorization server creates the access-token and uses the private key - and the RS256-algorithm - to sign the token
  2. The access-token is sent back to the client (together with the refresh token) and used to access the Web API
  3. Finally, the Web API uses the public key – and the algorithm - to validate and decode the token.

Two-factor Authentication

Two-factor authentication is supported using a one-time passwords (OTP) which is sent to an authenticator application on a mobile phone. So far, support for Google Authenticator is implemented by the Google Authenticator provider.

Two-factor authentication is easily configured by registering an OtpService in the Startup.cs file. You inject one or more OTP authenticators (IOtpAuthenticator implementations) into the service - for example the GoogleOtpAuthenticator from the Google Authenticator provider:

services.AddScoped(provider => new OtpService(new Dictionary<string, IOtpAuthenticator>() {
    { "GOOGLE", new GoogleOtpAuthenticator(TimeSpan.FromMinutes(1)) }
}));

The use of two-factor authentication is optional. If no OtpService is registered, two-factor authentication is not enabled. However, if enabled, the Google Authenticator app (or whatever other authenticator app is registered) must be installed on the users mobile phone.

OTP Registration is done from the api/tokens/otp/registration endpoint:

POST https://localhost:44355/api/tokens/otp/registration
Content-Type: application/json
{
  "id": "admin",
  "password": "mysecretpassword"
}

If the user is properly authenticated, this endpoint will return a setup code in the form of a QR code as well as a manual entry code. This setup code must be used to register the application in the Google Authenticator app.

GoogleAuthenticator

Once registration is done, the authenticator app will continuously generate a six-digit one-time password. Now, in addition to the the user ID and password, the request for access-tokens must include the one-time password (otp) and the corresponding OTP authenticator type (otpAuthenticator) as well:

POST https://localhost:44355/api/tokens
Content-Type: application/json
{
  "id": "admin",
  "password": "mysecretpassword",
  "otp": "251057",
  "otpAuthenticator": "GOOGLE"
}

IP-whitelisting

The two-factor authentication can also be used in combination with IP-whitelisting. In this case, the individual user will only be requested an OTP if the request does not come from a whitelisted IP-address.

This is configured using user groups. In the metadata of a particular user group, a metadata key called 2FAMetadata (configurable) must be defined. This key must contain a list of strings, where each string has the following format:

"{AuthenticatorType}:{AuthenticatorValue}&Comment:{comment}"

For example:

[
  "CIDR:192.43.239.119/32&Comment:NCI",
  "CIDR:194.223.165.122/32&Comment:DHI-AU-GC",
  "CIDR:192.43.239.119/32&Comment:NCI",
  "GOOGLE:6a4n2quwiivyswre"
]

The supported AuthenticatorTypes are at the moment "CIDR" (IP-whitelisting) and whatever OTP authenticators that are injected into the OtpService (e.g. "GOOGLE"). For CIDR, the AuthenticatorValue is a CIDR block and for GOOGLE, it’s the Google Authenticator setup code of 16 random characters.

In case one or more CIDR string are defined, the user will not be subjected to the second authentication step if the user passes the IP address white listing check. If the user does not pass the IP address whitelisting step, the user will be subjected to the alternative OTP (e.g. Google Authenticator) check.

If the CIDR check fails and another validation mechanism is defined, the token endpoint (api/tokens) returns a list of possible OTP authenticators. Now the token endpoint can be called again, but now with the OTP and the corresponding authenticator type as described above:

POST https://localhost:44355/api/tokens
Content-Type: application/json
{
  "id": "admin",
  "password": "mysecretpassword",
  "otp": "251057",
  "otpAuthenticator": "GOOGLE"
}

In the case that one or more CIDR blocks is configured, but no OTP authenticator, then the user may only authenticate from within the defined networks.

User Accounts

Domain Services offers an out-of-the-box Accounts service for user account management. This is also the default authentication provider.

As with any other storage in Domain Services, also the user account storage is abstracted away in the form of the IAccountRepository that serves as an extensibility point where you can plug in alternative implementations of a user account repository.

Currently, Domain Services offers 4 different ways to store the user accounts. One lightweight option is to store the user accounts in a text file (accounts.json). This is the default user account repository. This is useful for example if the client application does not require a user login, but will always login with the same "system" user account.

However, for client applications that require user login - especially if the number of user accounts is (potentially) large - other relevant options are:

Furthermore, the Accounts service offers support for user account registration, activation and password reset.

Password Hashing

Password hashing is done using the cryptographically secure pseudorandom number generator (CSPRNG) salt and the PBKDF2 hashing algorithm.

Breached Passwords Protection

Through the years, hundreds of millions of passwords have been exposed in data breaches. This exposure makes them unsuitable for re-use as they're at much greater risk of being used to take over other user accounts. The have I been pwned web site exposes an API for searching these breached passwords.

The Domain Services Web APIs can be configured to protect against re-use of compromised passwords.

Password Policy

The Domain Services Web APIs can be configured to enforce strong passwords.

Progressive Delay

The Web APIs are protected against brute-force attacks using a progressive delay mechanism where every failed validation attempt from a given IP address will lead to an increasingly longer response time.

OAuth 2.0 Authorization Flow

Domain Services supports the OAuth 2.0 authorization flow called Resource Owner Password Credentials.

  1. The user (aka. the resource owner) will enter his/her username and password (and possibly a one-time-password, if two-factor authentication is enabled) into an input form exposed by the client (typically a browser application).
  2. These credentials are passed on to the authorization server.
  3. If the credentials are successfully validated, the authorization server will issue and return a combination of an access- and a refresh token.
  4. The client will use the given access token to access the resource server of the application.

It is possible to replace the out-of-the-box Authorization Server provided by Domain Services with another authorization server like Auth0 or MIKE Cloud IAM.

User Groups

Domain Services supports the concept of user groups. User group memberships can be used to configure the policy-based authorization.

User groups can also be used to define a much more granular access control to the individual entities of a repository. This access control is based on so-called permissions associated with the individual entities. A permission allows a list of principals to perform a specific operation (for example "read") on an entity. Such principals will typically be user groups. The below example shows a JSON representation of some permissions on an entity:

{
  ...
  "permissions": [
    {
      "principals": [
        "Administrators",
        "Editors"
      ],
      "operation": "read"
    },
    {
      "principals": [
        "Administrators"
      ],
      "operation": "update",
      "type": "Allowed"
    },
    {
      "principals": [
        "Administrators"
      ],
      "operation": "delete",
      "type": "Allowed"
    }
  ]
}

Policy Based Authorization

The Web APIs are guarded by policy-based security. The policies "AdministratorsOnly" and "EditorsOnly" must be defined in the Startup.cs file. If the authentication provider can provide roles or groups, these claims can be used to define such policies:

// Authorization
services.AddAuthorization(options =>
{
    options.AddPolicy("AdministratorsOnly", policy => policy.RequireClaim(ClaimTypes.GroupSid, "Administrators"));
    options.AddPolicy("EditorsOnly", policy => policy.RequireClaim(ClaimTypes.GroupSid, "Editors"));
});

Access to administrative resource types such as Accounts and Connections are guarded by the AdministratorsOnly policy, while adding, updating and removing other resource types typically are guarded by the EditorsOnly policy.

Secrets

Secrets such as database connection strings or cryptographic keys should never be stored directly in configuration files - or directly in the code for that matter. Domain Services supports a convention where a string containing a sub-string of the format "[env:{environment variable}]" can be resolved at runtime to the value of the given environment variable.

The below example demonstrates the configuration of a public encryption key in the appsettings.json file.

"Tokens": {
    ...
    "PublicRSAKey": "[env:PublicRSAKey]"
}

Then the Resolve() string extension method can be used to resolve the environment variable in the startup.cs file:

Configuration["Tokens:PublicRSAKey"].Resolve()

React Web Components

A number of React Web Components are designed to work with the Accounts API and the User Groups API of Domain Services - for example a log-in component and a component for managing users and user groups.