Authorization Code with PKCE

OAuth 2.0 clients using the Authorization Code grant type can either be public or private. Public clients are those which cannot hold their credentials in a secure way. Since they don't hold their credentials, they are unable to use them when talking to the authorization server. Such applications are those installed by the user on their device, for example mobiles apps acquired from an app marketplace. Furthermore, these applications are vulnerable to the authorization code interception attack. After the user logged in on the authorization server and accepts the permissions, the authorization server redirects the user back to the redirect URI previously registered for that specific client application. The attack consists of a malicious application registering the same redirect URL, thus being able to steal the authorization code sent by the authorization server. Once the malicious app has the authorization code, it can then proceed to get an access token and it will be able to do so since it can communicate with the authorization server without needing credentials. You can read more in detail about the attack in the official RFC 7636.

Mitigating Against the Attack

The RFC 7636 exemplifies the attack and also provides a technique on how to mitigate against this problem. In a nutshell, the attack is mitigated by the client generating a secret on the fly. This secret generation on the fly is known as the "Proof Key for Code Exchange", AKA PKCE (pronounced "pixy").

The Authorization Code with PKCE Flow

  1. Authentication: The client generates a high-entropy random string called code_verifier
  2. Authentication: The client generates a hash from the code_verifier called code_challenge
  3. Authentication: The client sends the authorization request containing the code_challenge, the method used to hash it, along with the rest of params to the authorization server
  4. Authentication: User signs in with their Tapkey identity and negotiate scopes
  5. Authentication: If the user's sign in was successful, The authorization server returns the code to the client
  6. Authorization: The client then sends the code together with the code_verifier to the token endpoint
  7. Authorization: Before returning the access_token, the authorization server recomputes the hash using the code_verifier and the hashing method used by the client and compares it with the one sent initially during the authentication request.
  8. If they match, the client then receives the access_token
  9. Accessing protected resources: The client accesses the resource server (e.g. Management API, Mobile SDK) with the user's access token

Authentication

RFC 7636

This step corresponds with section 4 of the OAuth 2.0 Authorization Framework standard.

The client application first generates the code_verifier and then the code_challenge

var rng = RandomNumberGenerator.Create();

var bytes = new byte[32];
rng.GetBytes(bytes);

// It's recommended that the code_verifier be a URL-safe string
// See the Section 4 of the RFC 7636 for more details.
var code_verifier = Convert.ToBase64String(bytes)
    .TrimEnd('=')
    .Replace('+', '-')
    .Replace('/', '_');

var code_challenge = string.Empty;
using (var sha256 = SHA256.Create())
{
    var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(code_verifier));
    code_challenge = Convert.ToBase64String(challengeBytes)
        .TrimEnd('=')
        .Replace('+', '-')
        .Replace('/', '_');
}

Tip

You can find sample implementations for other languages in this article from Auth0

The client application then redirects the user to Tapkey's authorization endpoint located at

GET https://login.tapkey.com/connect/authorize

with the following query string parameters using the application/x-www-form-urlencoded format

Name Description
client_id Required. The client ID as issued during the Tapkey OAuth client application process.
redirect_uri Required. The URL in the client application where users will be sent after authorization.
response_type Required. Must be set to code.
code_challenge Required. The code challenge generated from the code_verifier
code_challenge_method Required. Must be set to S256 - Indicates that the challenge is hashed with SHA256
scope Required. A space delimited list of scopes. The available scopes can be found in the Scopes page.
response_mode Can be query or form_post. Defaults to query.
state An unguessable random string. It is used to protect against cross-site request forgery attacks.1 Typically 32 byte length.

Authorization

RFC 6749

This step corresponds with section 4.1.3 of the OAuth 2.0 Authorization Framework standard.

If the user accepts the client's request to gain access to selected resources, Tapkey's authorization server redirects the user back to the client with a temporary authorization code in a code parameter as well as the state provided in the previous step in a state parameter. If the states don't match, the process must be aborted. The redirect furthermore contains a scope parameter, that reflects the selection of scopes the user has actually granted. The code can then be exchanged for an access token (for accessing the Management API2) using the token endpoint

POST https://login.tapkey.com/connect/token
with the following parameters using the application/x-www-form-urlencoded format with a character encoding of UTF-8 in the request body

Name Description
client_id Required. The client ID as issued during the Tapkey OAuth client application process.
grant_type Required. Must be set to authorization_code.
redirect_uri Required. The same URL used during the Authentication phase. See below more details
code Required. The authorization code obtained during the Authentication phase.
code_verifier Required. The code_verifier (PKCE proof key) generated initially.

The client is returned an access token in exchange.

RFC 6749 - Redirect URL on token request

According to RFC 6749, 4.1.3. the redirect_uri parameter is required if it was included in the authorization request. Since the authorization server requires the redirect URL during the authorization request it must be sent also during the token request. This adds an extra layer of security as the authorization server will enforce that they match.

Refreshing an access token

RFC 6749

This step corresponds with section 6 of the OAuth 2.0 Authorization Framework standard.

Access tokens are issued by Tapkey with limited lifetime only (1 hour, subject to change). After an access token has expired, it can no longer be used to access the Management API. Doing so will result in a 401 - Unauthorized response from the Management API. In this case, the client has to either run through the entire process described above again or, if repetitive user interaction is undesirable, a refresh token can be used to retrieve a new access token from the authorization server.

Refresh tokens in native clients

As mentioned, native applications cannot hold secrets. This means that requests to the token endpoint to renew access tokens will be unauthenticated (the credentials are not sent). It is extremely important that you keep your refresh tokens stored in your native app in a secure way. Leaking the refresh token can lead to someone else accessing user data without you or the authorization server knowing.

The refresh token can be used to retrieve an access token using the token endpoint

POST https://login.tapkey.com/connect/token
with the following parameters:

Name Description
client_id Required. The client ID as issued during the Tapkey OAuth client application process.
grant_type Required. Must be set to refresh_token.
refresh_token Required. The refresh token issued to the client.
scope The scope of the access request as described by Section 3.3. The requested scopes MUST NOT include any scope not originally granted by the resource owner, and if omitted is treated as equal to the scope originally granted by the resource owner.

Important

To be able to get refresh tokens, your application must request the offline_access scope during the Authentication request.

The authorization server uses sliding expiration semantics for refresh tokens. This means refresh tokens are issued with a lifetime of 90 days. Every time you call the token endpoint using the Refresh Token grant type, you'll get:

  1. A new access_token valid for 1 hour
  2. The same refresh_token that was passed. As a good practice, you should replace your existing one even though it's the same.
  3. The lifetime of the refresh token is now + 90 days.

This gives clients a good flexibility to get new access tokens without having to make the users go through the same process again. If your app does not call the token endpoint in 90 days, your refresh token will expire. In this case, the whole flow has to be initiated again.

The following snippet shows an exemplary response of a token refresh request:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ij...j8vltwIXCCxGV2D9xm82tx-A",
  "expires_in": 3600,
  "token_type": "Bearer",
  "refresh_token": "4a4a8fb5c4ef170a9e07e30a2f5...f1dc4f955d72c77e1ceb76ff2e1"
}

Accessing protected resources

After obtaining an access token, it can be used to access the resource server (e.g. call the Management API or authenticate users in the Tapkey Mobile SDK).

Management API

The access token can be used to call the Management API to manage grants, view logs or query the users locking devices. Take a look at the Operations section to see all the endpoints available.

The following snippet demonstrates an exemplary call to the Management API, requesting additional user information.

RFC 6750

The authorization method used in this example is specified in section 2.1 of RFC 6750.

GET https://my.tapkey.com/api/v1/auth/auth/userinfo

Authorization: Bearer eyJhbGciOiJSUz...O-YbBq8F7086rQi-kEbERp4dA3r0WonpHnmYcXEnA
will return the authenticated user's details, e.g.:
{
  "ownerAccounts": ...,
  "ownerAccountPermissions": ...,
  "id": "9da7db0a-....-....-....-1c81a6060daf",
  "ipId": "com.auth0",
  "ipUserName": "...@tapkey.com"
}

Mobile SDK

The following snippet demonstrates how to log in a user in the Tapkey Mobile SDK using the access token retrieved through the Authorization Code with PKCE flow.

UserManager userManager = tapkeyServiceFactory.getUserManager();
userManager.logInAsync("eyJhbGciOiJSUz...O-YbBq8F7086rQi-kEbERp4dA3r0WonpHnmYcXEnA", ct)
    .continueOnUi(userId -> {
      // User logged in, use user's ID here.
    });
Sample Application

You can have a look at a sample ASP.NET Core MVC application available on our GitHub that lists the user's locking devices using the Authorization Code with PKCE flow.

Although the sample is using .NET, you can easily re-use the concepts and build the same thing using your language/framework of choice.