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 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¶
- Authentication: The client generates a high-entropy random string called
code_verifier
- Authentication: The client generates a hash from the
code_verifier
calledcode_challenge
- 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 - Authentication: User signs in with their Tapkey identity and negotiate scopes
- Authentication: If the user's sign in was successful, The authorization server returns the
code
to the client - Authorization: The client then sends the code together with the
code_verifier
to the token endpoint - Authorization: Before returning the
access_token
, the authorization server recomputes the hash using thecode_verifier
and the hashing method used by the client and compares it with the one sent initially during the authentication request. - If they match, the client then receives the
access_token
- Accessing protected resources: The client accesses the Tapkey Web APIs (e.g. the Tapkey Access Management Web API) 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 is recommended to use a URL-safe string as code_verifier.
// See section 4 of 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 Web APIs2) using the token endpoint
POST https://login.tapkey.com/connect/token
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 Tapkey Web APIs. Doing so will result in a 401 - Unauthorized
response from the Web 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
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:
- A new
access_token
valid for 1 hour - The same
refresh_token
that was passed. As a good practice, you should replace your existing one even though it's the same. - 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 Tapkey Web APIs (e.g. call the Tapkey Access Management Web API or authenticate users in the Tapkey Mobile SDK).
Tapkey Web APIs¶
The access token can be used to call the Web 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 Web 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
{
"ownerAccounts": ...,
"ownerAccountPermissions": ...,
"id": "9da7db0a-....-....-....-1c81a6060daf",
"ipId": "com.auth0"
}
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.