JWT Authorization Grant

To obtain an access token for your service account, or for your end-user, the JWT Authorization Grant is used. This OAuth grant type is defined in JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants (RFC-7523 2.1), which is an extension specification of the OAuth 2.0 framework.

đź’ˇ Why not use the Authorization Code Grant?

The Authorization Code Grant is probably the most commonly used OAuth 2.0 grant type. Its purpose is to ask an end-user (resource owner, in OAuth speak) permission for a client application (your application) to use their account (access their resources). The user's permission is expressed as an authorization code that is used by the client application to obtain an access token. This grant type is meant for situations where there is no trust relationship between the client application and the user.

This does not match with a Drillster Platform integration very well for a number of reasons:

  • When accessing your organization's resources, the user is supposed to be a service account, which is not a person. A service account cannot log in and grant your application permission to use its account.
  • When accessing your organization's resources through one of your regular administrative user's accounts, your integration would depend on this account as long as it is used. This would make your integration quite vulnerable to changes to the administrator's account. Also, the access and refresh tokens required for such an approach will expire eventually, requiring your administrator to perform the authorization code grant again.
  • When obtaining an access token for one of your users, there is a strong trust relationship between your application and the user — it's one of your own users. Asking for permission to use their account would be unnecessary, and very unnatural.

Obtaining an OAuth access token

The process of getting an access token is as follows:

  1. Create a JWT to request an access token for the service account user.
  2. Sign the JWT with a fixed and previously obtained private key.
  3. Exchange the signed JWT to obtain an access token at Drillster's token endpoint.

Access tokens created by this process have a limited validity period, and are not accompanied by a refresh token. Typically an API integration would generate a new access token for each operation or batch of operations. An access token may be requested as many times as needed. Instead of using refresh tokens the integration relies on a private key. The use of refresh tokens is not well suited for API integrations, as a single failed token refresh operation causes the integration to stop functioning.

⚠️ Private keys are secret!

The private key must be kept secret under all circumstances. This means that a private key must never be used in a public client such as a web application or a mobile app. The use of this grant type is specifically designed for server-to-server API integrations.

Requirements

The following things are required in order to successfully obtain OAuth access tokens through the JWT Authorization Grant:

  • Register a client application – All API requests are made by a client application. Please see the documentation for more information on how to do that. You will need the Client ID and Client secret for your integration.
  • Set up a service account – Please set up a service account including the appropriate permissions and group access.
  • Create a key pair for the service account – Please see the service account documentation for more details.
  • Implement the code to create a signed token request and obtain access tokens – This is the least trivial part. Luckily there are many existing software libraries that can take care of this. Please see below for more information.

Creating a signed JWT

The creation of a signed JWT should be automated, ideally using existing, industry standard libraries. However, what follows is an explanation of how this can be done manually for illustrative purposes.

The ingredients of a JWT request are:

  • The signing algorithm. This can be gleaned from the JSON key file. The example key is created according to RSA_2048. This corresponds to a signing algorithm of RS256 in JWT speak.

  • The key ID. This value can also be taken from the JSON key file – it is listed as keyId.

  • The user ID of the service account. This too is listed in the JSON key file, as serviceAccountId.

  • (Optional) The target user ID for whom an access token is being requested. This flow may be used to request an access token for the service account itself, or for a user that is managed by the organization.

  • The URL of Drillster's token endpoint. This is the endpoint where the signed JWT can be exchanged for an access token. This endpoint is advertised at https://www.drillster.com/.well-known/oauth-authorization-server, under token_endpoint. The current location of the token endpoint is https://www.drillster.com/daas/oauth/token. The endpoint location is not likely to change, but if it does change it will be advertised in the above “well known” location.

  • An expiry timestamp. Each JWT request must have an expiry timestamp which is in the future, but not more than one hour. The format is seconds since the Unix epoch (i.e. since 1970-01-01 00:00:00 UTC). For the purpose of this example you can for example use the date +%s command on most Unix-based systems. Be sure to add some seconds to the value to place it into the future (but not more than an hour). You could for instance add 1800 to the value to give yourself 30 minutes to use your JWT. A typical timestamp looks something like this: 1607020363.

This example uses the đź”— JTW.io website as a handy tool to manually construct, sign and check JWTs. Once you have assembled all the ingredients, please head over to JWT.io.

A JWT consists of a header, a payload (also known as the Claims Set) and a signature. In our case the JWT header is constructed as follows:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "cf9f895ff1f64e2f9ceea45074f56c52"
}

The fields are:

  • alg – the algorithm
  • typ – always equal to JWT
  • kid – the key ID

The payload (Claims Set) is constructed as follows:

{
  "sub": "iqKpEF3URCe0yAsyrsk_4g",
  "iss": "iqKpEF3URCe0yAsyrsk_4g",
  "aud": "https://www.drillster.com/daas/oauth/token",
  "exp": 1607020363
}

The fields are:

  • sub – The user ID for whom an access token is requested, i.e. either the service account ID itself, or an account ID of an account managed by the organization that the service account is part of. Note that for human users the sub claim may also be populated with an email address or a third party ID. For service accounts, the user ID may be found in the JSON key file.
  • iss – The user ID of the service account, i.e. the issuer of the JWT.
  • aud – The URI of the token endpoint.
  • exp – The expiry timestamp

Fill in the header and payload into the designated input boxes on JWT.io.

Finally the JWT needs to be signed with the private key. In the JWT.io utility this can be done by pasting the private key into the “private key” box of the signature section. This should generate a signed JWT on the left hand side of the screen. You can also paste in the public key to verify the key pair, but this is not necessary to create a signed JWT.

If all went according to plan you now have a valid, signed JWT that can be used to obtain an OAuth access token (if used within the expiry period).

Requesting the access token

Exchanging a signed JWT for an access token is simply a matter of offering it to the token endpoint. The format is as follows:

POST https://www.drillster.com/daas/oauth/token
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
client_id=874a16d4ac764ce4a545f0cca4584c63
client_secret=5782b2e7532b48b5a0798f2ad6644614
assertion=eyJhbGciOi…ZmQ4NjFiZT.eyJzdWIiOi…YwNzAxNTM2.SLdboQsTud…dMLYvI75sA

The parameters are:

  • grant_type – always equal to urn:ietf:params:oauth:grant-type:jwt-bearer
  • client_id – the client ID for your registered client application
  • client_secret – the client secret for your registered client application
  • assertion – the signed JWT

A successful response typically looks like this:

{
  "access_token":"eyJhbGciOiJSUzI1NiIsInR5cI6IkpXVCJ9.eyJzdWIiOiJO1NNNUQ0VlQ2dWZpcENMWkh0V0FRIiwiZXhwIjoxNjA3MDE1MzczLCJqdGkiOIwNGI4MWIyNy0wOGU2LTQzMjQtOTU4Ni1lMGMzYmZhNWMzMWEiLCJjbGllbnRfaWQiOiI3YTMwMzAzYWRhN2M0MWZlOThmZDFlMmY0OTY2YTA4NSIsInNjb3BlIjpbIlJPTEVfVVNFUiJdfQ.SrzNkXIlR9XHUy5QGAsod4wD7l1n2uUG0UmyyHaSFf0E4qOiGItdOn-riU__kNgWAfrGHEytxML6Fry3G901sCfF3dNTXRGcNDi28P_sCEynR2VKBeEPZxxIq47-nhLyMQwvl3TWaesQxrUDNQf3gMxh8bGtjLhai8Ov4lPdKvnGw9Y9kE12vyLCZVPHePnlLea3_2LWIln484ql3JChHgi8JeQj_Wr_1pFmXqznTZ6e5UCnP7WL4p4tsTsGlw1IGdCiQ3XuM6Lu3r0N8gWVfaefScJyppZRwvhx_7WfeUaOBSbE1ntW_IcE3ADE2a1hENKDj2b3VhEHmdHeDk7yQ",
  "token_type":"bearer",
  "expires_in":3599,
  "scope":"ROLE_USER",
  "jti":"04b81b27-08e6-4324-9586-e0c3bfa5c31a"
}

Simply pick up the value for access_token and use it as a bearer token in your API requests. Once the access token expires it cannot be refreshed – a new token must be requested again. A new access token may be requested at any time, before or after expiry of the current token.

 

Last updated on