Authentication Schemes

Introduction

As explained in the security pipeline article, ASPSecurityKit supports multiple authentication tokens – also known as authentication schemes – and it’s fairly easy to add a handler for custom scheme in the pipeline if you have a need.

In this article, we’ll give in-depth details of the built-in authentication schemes – how to use each, which one is suited for what scenario, associated concepts, sources supported, and validations.
But before going through the individual schemes, we should go through a common identity validation logic that all the schemes go through.

Validating identity tokens

The auth token is validated by its scheme handler. But the identity token referred in the auth token has also certain validation. The IAuthSessionProvider.GetValidAuthDetails is expected to perform such validations and only return a valid identity object associated with the given authUrn.

The default implementation – AuthSessionProvider – provides IsAuthValid method which performs some validations already, If you have a need for more, you can add those by overriding it.

Ineffective/Expired

Validates whether the identity is in effect (effective greater than current UTC time) and has not expired (Expired less than or equals to current UTC time).

IP firewall

Before the firewall validation is applied, the following conditions are checked to determine if it’s applicable to the requested operation:

Once it’s determined that the firewall is applicable, IPRanges is enumerated and if there’s any range that encompasses the caller IP, the request is allowed to proceed. If there’s no firewall record the request is still not allowed. So you should add ranges for whole of the internet – one for both IPV4 and IPV6 protocols – to allow such tokens user sessions to pass through the firewall.

If firewall check fails, an AuthFailedException is thrown with code as IpDenied and error message as IpNotAllowed.

Origin restriction

Public API keys support a cosmetic restriction thereby any request not originating from the whitelisted origins isn’t allowed to proceed. We call it cosmetic because this isn’t a security provision as it’s fairly easy to spoof the origin header sent in the request.

But this restriction is useful to prevent accidental use of public API keys from unintended deployments. For example, it can catch SPAs on non-production environments calling the production APIs thereby preventing the insertion of junk data on production.

Since public keys serve the purpose of recognizing different integrators and their front-end systems, it helps in applying a reasonable restriction preventing vendors to use someone else’s public API key on their websites to call your public APIs.

The origin value is obtained from either the official HTTP origin header or the custom header by the name defined by XAskRequestOrigin. The latter is used only if the first isn’t available which we had observed happening to some requests in ISCP, so ajax request logic was updated to always send this header.

If origin is missing from both the sources, an AuthFailedException is thrown with code as InvalidOrigin and error message as OriginMissing.

The allowed OriginDomains list is enumerated and if there’s any pattern that matches the request’s origin, the request is allowed to proceed. If there are no permitted origin domains the request is still not allowed. So you should add a “*” pattern record to allow all origins.

If origin check fails, an AuthFailedException is thrown with code as InvalidOrigin and error message as OriginNotAllowed.

Site key restrictions

Site keys are sensitive keys given that these provide client-wide access to the data within the system. ASPSecurityKit attempts to identify and raise a flag on potential incorrect uses of site keys to prevent leakage of data.

The first of these checks is to detect if a site key is used for the request coming from a browser client. It’s possible that during development, a developer might be tempted to hard-code site key in the javascript logic to quickly test a new feature being developed bypassing the need to authenticate via sessions. However, this is quite dangerous because if you forget to remove the site key credentials from the logic, it’ll be exposed to the public on production systems and it’s not difficult to imagine the grave consequences such exposure can cause.

For this reason, requests coming from a browser client using a site key are generally rejected with an error SiteKeyNotAllowed. The browser client is detected based on either of the following:

  1. Does the request have an origin header? (Browsers usually set this header.)
  2. Does the UserAgent represent a browser?

However, if the authentication method used for the request is Service HMAC, the browser restriction isn’t imposed on site keys because Service HMAC token is created by your web application itself for callback URLs which can be invoked via a browser redirects as in case of DocuSign signature flow.

Another restriction imposed is to not allow a site key to be used as Service Key token by checking whether its KeyBasedAuthAllowed flag is set. If it is, an error is thrown with error message as SiteKeyBasedAuthNotAllowed. Using site keys as Service Key token is dangerous because site keys are expected to have cliend-wide data access permits. You should instead use feature keys for Service Key tokens with only limited permits required for the integration.

HMAC scheme

An HMAC auth token protects sensitive credentials from leaking and makes requests tampered-proof by requiring that client includes a unique signature – called the HMAC – in the auth token for the request, computed using request parameters and a shared secret. The server then uses this token to identify the client and verify the request. To have an in-depth conceptual understanding of HMAC, read the guide on implementing HMAC scheme.

The HMAC is best suited for API platforms as it requires computation of the signature on the client side. The DefaultHmacTokenHandler type implements IAuthenticationSchemeHandler for HMAC support in ASPSecurityKit. It can accept HMAC token from following sources:

  • Authorization header: Embedded in the request’s authorization header and identified by the scheme name defined by AuthToken.HeaderHmac value. This source is supported for all requests if the handler is injected into the AuthenticationProvider. An example:
Authorization: ask-hmac sessionid:689c727e23c94f388a5a9e1dbf83a100:YO0C4/lTYQN9oKd+Qt8WuK6bHPDv8stN1Q+LDzJWz1M=:3b661b70a71345fc860c4489d1c0e095:1605180631
?AskAuth=sessionid:689c727e23c94f388a5a9e1dbf83a100:YO0C4/lTYQN9oKd+Qt8WuK6bHPDv8stN1Q+LDzJWz1M=:3b661b70a71345fc860c4489d1c0e095:1605180631

The reason behind default restrictions on query string based HMAC token is that you don’t want people to be able to create signed request URLs of random endpoints and share them with other folks. So you explicitly designate the endpoints for which you enable this feature.

HMAC token in URL query is especially useful for download endpoints which are executed as regular HTTP get request wherein you don’t have a possibility to send the authorization header.

Note: You can put security attributes like AcceptHmacTokenInQueryStringAttribute on controller or base controller and all the child/descendant actions will automatically have the feature enabled. The same can be done in ServiceStack with a common base request DTO.

HMAC components

Following components are used to compute the signature:

  1. AuthUrn – As is.
  2. Verb – Upper case.
  3. URL – Lower case. All components including scheme and query string. Variations are also tried to handle some encoding differences.
  4. Timestamp (UNIX) – Total seconds elapsed since UNIX epoch start (1970-01-01 0:0:0).
  5. Nonce – A random value (typically a GUID).
  6. Message Body – Base64 MD5 hash of the request body (if there is one). Subject to exclusion.

The HMAC auth token is then composed as follows: authUrn:signature:nonce:timeStamp.

sessionid:689c727e23c94f388a5a9e1dbf83a100:YO0C4/lTYQN9oKd+Qt8WuK6bHPDv8stN1Q+LDzJWz1M=:3b661b70a71345fc860c4489d1c0e095:1605180631

Excluding body

Another important option supported by DefaultHmacTokenHandler is the ability to exclude consideration of request body from HMAC signature computation. By default the request body also forms one of the input components used in HMAC computation. However, for certain endpoints – such as the upload ones – you may not prefer or find it impossible to consider file contents for HMAC computation. You can therefore designate such endpoints to not consider body for HMAC by marking it with RequestFeature.ExcludeBodyFromHmac (use ExcludeBodyFromHmacAttribute in ASP.NET or ServiceStack to indicate the same).

This feature requires that the request is made over Secure connection; if it isn’t, an AuthFailedException with Unauthenticated as code and InsecureConnection as error message will be thrown.

Expiration and replay prevention

The expiry window for HMAC is determined by HmacMaxAgeInSeconds settings which is default to five (5) minutes. If HMAC token is detected to be older than this window, an error with message HmacExpired is thrown.

To protect against replay attacks, the DefaultHmacTokenHandler checks if the cache client contains the incoming signature. If it does, an error with message ReplayRequest is thrown. Otherwise, a cache item with incoming signature as the key is added to the cache with expiry window as specified by the HmacMaxAgeInSeconds setting.

Handling variations

Another important factor considered by the DefaultHmacTokenHandler is handling of variations in URL encoding of some special characters by different browsers or .NET encoder. For this purpose, the DefaultHmacTokenHandler makes multiple attempts to match the signature – one for each encoding variation – when it encounters one or more of those special characters in the URL. Currently it tries following variations:

  • If it detects URL has a string ‘%27’, it adds a variation by replacing ‘%27’ to apostrophe (') because JS encodeUriComponent doesn’t encode apostrophe while .NET encoders do.
  • It does the same for ‘%7e’ and adds a variation replacing ‘%7e’ to the character tilde (~).
  • It also adds a decoded (using HttpUtility.UrlDecode function) URL as another variation to try.

Another type of variation it handles is related to an incoming signature. Since the signature is base64 encoded, plus (+) character is a valid value. However, that is converted to the space character automatically by .NET URL modules. So, if a space character is detected in the incoming signature string, a variation of the signature is added with plus replacing every instance of the space character. Both of these are then compared with the computed signature to see if there’s a match.

JS client example

Here we give an example of computing signature and building HMAC auth token in JavaScript language. You can adapt it for your SPAs or any other client. Following dependency is required:

var idType = ''; // the identity token type (sessionid or apikey, for example)
var idToken = ''; // the identity key value
var base64Secret = '';

function newGuid() {
    return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : r & 0x3 | 0x8; return v.toString(16); });
}
 
function epochTime() {
    var d = new Date();
    var t = d.getTime();
    var o = t + "";
    return o.substring(0, 10);
}
 
function createHeaderHmac(request) {
    var time = epochTime();
    var nonce = newGuid();
    var method = request.method.toUpperCase();
    var encodedUri = encodeURIComponent(request.url).toLowerCase();
     
    var b64BodyContent = "";
    if (JSON.stringify(request.data) != "{}") {
        b64BodyContent = CryptoJS.MD5(request.data).toString(CryptoJS.enc.Base64);
    }
    var authUrn = idType + ":" + idToken;
    var rawSignature = authUrn + method + encodedUri + time + nonce + b64BodyContent;
    var rawSecret = CryptoJS.enc.Base64.parse(base64Secret);
    var signature = CryptoJS.HmacSHA256(rawSignature, rawSecret).toString(CryptoJS.enc.Base64);
     
    return "ask-hmac " + authUrn + ":" + signature + ":" + nonce + ":" + time;
}

Service HMAC (SHMAC) scheme

As introduced in the HMAC guide, SHMAC token is a special-purpose form of HMAC token which is more flexible. It’s meant to be used for securing callbacks (webhooks and others) from third-party services (TPS), if the service supports specifying the callback URL dynamically.

The ServiceHmacTokenHandler type implements IAuthenticationSchemeHandler for SHMAC support in ASPSecurityKit. It only accepts an SHMAC auth token Embedded in the request URL’s query string – identified by the key name defined by AuthToken.QSServiceHmac value. This is because usually you can’t configure a callback to send request headers.

Since not all endpoints are supposed to act as callbacks, the ServiceHmacTokenHandler also requires that the requested operation is explicitly marked with RequestFeature.ServiceHmacToken (use AcceptServiceHmacTokenAttribute in ASP.NET or ServiceStack to indicate the same).

Note: You can put security attributes like AcceptServiceHmacTokenAttribute on controller or base controller and all the child/descendant actions will automatically have the feature enabled. The same can be done in ServiceStack with a common base request DTO.

Creating a SHMAC auth token

The ServiceHmacTokenHandler also implements IServiceHmacTokenProvider which provides a Sign method, which takes amongst other things, a URL to sign, SHMAC metadata etc. and returns a signed URL (having SHMAC token embedded as query string parameter). You can then use this signed URL to configure callback on the TPS.

SHMAC components

Following components are used to compute the signature:

  1. AuthUrn – As is.
  2. Verb – Upper case.
  3. URL – Lower case. If restricted to specified query parameters, the query string is entirely removed.
  4. Data params – Lower case. If restricted to specified query parameters, such params are taken from the query string as key=value and concatenated without any delimiter.
  5. Timestamp – If validity duration is specified, a timestamp is also considered in the format currentUnixTime|validityDurationInSeconds.

The SHMAC auth token is then composed as follows: authUrn:signature:timeStamp:dataParams. Where dataParams includes only the names of the parameters specified for signature computation. Hence the timestamp and dataParams components are optional in SHMAC.

&AskSHTAuth=sessionid:689c727e23c94f388a5a9e1dbf83a100:2Ka1Slmi1mKwJbL2ZfS4kx5XrtTzj3jHdWfD0WmvrC0=:1605249745|172800:format

By including the metadata in the signature computation and then in the token string, the token becomes self-sufficient for signature validation upon callback.

Expiration and replay prevention

SHMAC doesn’t enforce replay prevention as compared to HMAC because TPS can execute callbacks more than once for valid reasons.

The expiration is also dynamic – configurable per callback endpoint – as mentioned in the prior section.

Handling variations

Just like HMAC, ServiceHmacTokenHandler also handles the variation related to incoming signature. Since the signature is base64 encoded, plus (+) character is a valid value. However, that is converted to the space character automatically by .NET URL modules. So, if a space character is detected in the incoming signature string, a variation – with plus replacing space – of the signature is also compared with the computed signature to see if there’s a match.

However, no URL variations are attempted reason it being a backend-to-backend communication and the URL isn’t composed by the TPS; it merely invokes the given one (though TPS will likely add some status information at least).

AuthCookie is a cookie based authentication scheme that’s similar to FormsAuthentication cookie in ASP.NET. However, the AuthCookie stores a proper auth token as value including authUrn. Thus you can use any type of identity – such as session or APIKey – to create cookie as you do with HMAC etc.

The AuthCookie is best suited for MVC applications because the requests are usually regular HTTP get/post (compared to AJAX in case of APIs)

The AuthCookieHandler type implements IAuthenticationSchemeHandler for AuthCookie support in ASPSecurityKit. It requires that the requested operation is explicitly marked with RequestFeature.AuthCookie (use AcceptAuthCookieAttribute in ASP.NET or ServiceStack to indicate the same).

Note: You can put security attributes like AcceptAuthCookieAttribute on controller or base controller and all the child/descendant actions will automatically have the feature enabled. The same can be done in ServiceStack with a common base request DTO.

The AuthCookieProvider implements IAuthCookieProvider which provides a CreateAuthCookie method, which takes amongst other things, the authUrn, its secret and cookie options.

If options argument isn’t specified (null), AuthCookieOptions setting value is used instead.

If persist argument is true the cookie is created with an expiration value either based on ExpiresIn options value, or RememberMeTimeoutInMinutes settings if former is null.

The cookie is created with the scheme name defined by AuthToken.Cookie value. The value format for AuthCookie is authUrn:authUrnHash where authUrnHash is a SHA256 HMAC computed using the secret associated with the identity token.

However, for using AuthCookie you don’t need to worry about all these lower-level details – you can simply employ it using the higher-level constructs.

To use AuthCookie with sessions, do the following:

  1. Upon call to Login action, invoke AuthSessionProvider.Login with createAuthCookie argument as true.
  2. Upon call to Logout action, invoke AuthSessionProvider.Logout.
  3. As AuthCookie is detected during the authentication step of the security pipeline execution, call to AuthSessionProvider.GetValidAuthDetails is made. This method makes call to SlideExpiration which is responsible to handle the session sliding for both short-term or persistent, long-term sessions.

As you saw above, you need to implement certain methods of IIdentityRepository for session management. Not a problem! With ASPSecurityKit Essential source package, you get all these implementations as source files installed right into your project for no extra cost! If you buy a Starter or higher source package, you’ll even get the end-to-end implementation of Register, login/logout actions including MVC UI!

ServiceKey scheme

ServiceKey is a simple key-based authentication scheme that establishes the identity based on just the APIKey identity token.

The ServiceKey is best suited for API platforms – especially for integrating with services that do not provide a possibility for building dynamic Service HMAC token for requests/callbacks. Examples are payment gateways that only provide configuration of static webhook URLs. You should only use ServiceKey when HMAC isn’t possible for authentication as a mere leak of the API key can render the system vulnerable to data thefts.

The ServiceKeyHandler type implements IAuthenticationSchemeHandler for ServiceKey support in ASPSecurityKit. It only accepts ServiceKey token Embedded in the request URL’s query string – identified by the key name defined by AuthToken.QSServiceKey value. Example:

curl -X GET "https://api-demo.ASPSecurityKit.net/cultures?AskKey=56af80d984fa4f1dad1e352ff7e85213"
-H "Content-Type: application/json"

Options and best practices to reduce threats with ServiceKey

  1. Use it Only on a secure connection. This way you mitigate the possibility of leaking the key to a MIM adversary. The ServiceKeyHandler outright rejects use of ServiceKey over unsecure connections.
  2. Restrict endpoints that can use ServiceKey for authentication. The ServiceKeyHandler requires that the requested operation is explicitly marked with RequestFeature.ServiceKey (use AcceptServiceKeyAttribute in ASP.NET or ServiceStack to indicate the same).
    Note: You can put security attributes like AcceptServiceKeyAttribute on controller or base controller and all the child/descendant actions will automatically have the feature enabled. The same can be done in ServiceStack with a common base request DTO. But as mentioned above, do not give a blanket access on production systems via ServiceKey.
  3. Create special purpose API keys to act as ServiceKeys and assign each with only permissions it needs. If PermitGroupId is specified for the token, the user service loads only group permits rather than all user permits.
  4. Have a special flag so general-purpose API keys cannot be used for ServiceKey authentication. This prevents access to endpoints (having ServiceKey authentication enabled) with general-purpose keys used as ServiceKey because such keys are knowable from HMAC tokens, wherein the key is sent as plain-text. The ServiceKeyHandler requires that KeyBasedAuthAllowed is marked as true to use the APIKey for ServiceKey. It denies authentication otherwise – even though the APIKey used is perfectly valid.
  5. Lastly, you should enforce mandatory IP firewall on ServiceKeys so only requests from the intended service can invoke the request. This way even if the key is leaked, ASPSecurityKit will deny its usage from non-whitelisted IP addresses.