OpenId Connect Authentication with Authorisation Code Flow

We strongly recommend the use of standardized and validated client libraries from you application code. Security is very difficult to get correct and this article will just show a simplified minimal implementation of the authentication of a user with OpenId Connect (OIDC), more in the purpose to educate the basics than showing production ready code.

Initiating user authentication

The application will initiate a user authentication by re-directing the browser to the authorization endpoint of the Identity Provider (IdP, in this case Authway). The re-direct must be an OIDC authentication request message. Here is an example of the re-direct by the application:

HTTP/1.1 302 Found
Location: https://YOURINSTANCE.irmciam.se/connect/authorize?
          client_id=YOUR_CLIENT_ID
          &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcallback
          &response_type=code
          &scope=openid
          &state=YOUR_STATE

Explanation of the request parameters in the example:

  • client_id: The application (Client) identifier in Authway. This identifier is created when the application is registered in Authway.
  • redirect_uri: The callback URI where the application want to retrieve the authentication response. This URI is validated by Authway and it must be an exact match of a registered redirect URI.
  • response_type: Set to code to indicate that it is the authorisation code flow that should be used.
  • scope: Used to specify the requested authorisation in OAuth. openid indicates a request for OIDC authentication and a ID token. More scopes can be supplied to indicate what information should be included in the ID token.
  • state: Value set by the application to keep state between the request and the callback.

There are more parameters that can be used.

At the Idp the user will be prompted to sign-in, if that is not already the case. After the Idp has successfully authenticated the user it will call the application redirect_uri with an authorization code (or an error code if Idp fails).

HTTP/1.1 302 Found
Location: https://client.example.org/callback?
          code=A_CODE_ISSUED_BY_IDP
          &state=YOUR_STATE

The application must validate the state parameter and if successful continue to exchange the code for the ID token.

Exchange code for ID token

The retrieved code is meaningless to the application and must be exchanged for an ID token by making a back-channel call to the Idp. By making this in a back-channel the client can be validated, so that the Idp is only revealing tokens to trusted applications, and the ID token is never exposed to the browser (which is more secure).

The code can be exchanged at the token endpoint:

POST /connect/token HTTP/1.1
Host: YOURINSTANCE.irmciam.se
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

grant_type=authorization_code
 &code=A_CODE_ISSUED_BY_IDP
 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcallback

Client ID and secret can be passed in the Authorization header or as client_id and client_secret parameters in the request body. The form-encoded parameters should include a repeated redirect_uri and the retreved code from the response after the successful authentication of the user.

If the Idp handles the request successfully it will return a JSON object with the ID token, an access token and optionally a refresh token together with expiration information.

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
  "id_token": "A_JWT_TOKEN",
  "access_token": "AN_ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
}

The ID token should be validated by the application before it is accepted.

The Authorisation Code Flow with PKCE

When using the Authorization Code Flow in public clients, such as single-page applications (SPAs) or mobile apps, it is strongly recommended to use PKCE (Proof Key for Code Exchange) to enhance security. PKCE mitigates the risk of an authorization code being intercepted and used by a malicious actor.

With PKCE, the client creates a code verifier and a code challenge before initiating the authorization request. These values are used to bind the authorization code to the specific client instance that requested it.

Here is an example of the re-direct by the application:

HTTP/1.1 302 Found
Location: https://YOURINSTANCE.irmciam.se/connect/authorize?
          client_id=YOUR_CLIENT_ID
          &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcallback
          &response_type=code
          &scope=openid
          &state=YOUR_STATE
          &code_challenge=YOUR_CODE_CHALLENGE
          &code_challenge_method=S256

Explanation of the additional request parameters in the example:

  • code_challenge: A derived value from the code verifier. It is created by applying the SHA256 hash algorithm to the code verifier and then Base64URL-encoding the result. This value is sent in the initial authorization request.
  • code_challenge_method: Specifies the transformation method used to generate the code challenge. The recommended and most secure method is S256. The plain method (plain) is also defined but should only be used if the S256 method is not supported.

Exchange code for ID token with PKCE

When exchanging the code for the ID token must also be modified when PKCE is used. The client (application) must include the code_verifier that was used to generate the code_challange in the authorisation request.

The extended code that exchange the code at the token endpoint:

POST /connect/token HTTP/1.1
Host: YOURINSTANCE.irmciam.se
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
 &code=A_CODE_ISSUED_BY_IDP
 &code_verifier=YOUR_CODE_VERIFIER
 &client_id=YOUR_CLIENT_ID
 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcallback

Note that the this request does not pass any Authorization header, because a public client can’t securely store a client secret, since their code and configuration can be accessed by the user. This is the reason why PKCE is used and strongly recommended for this type of clients, but can be used by confidential clients (such as traditional server-side web applications) too for improved security (together with the client secret).

Explanation of the additional parameter:

  • client_id: The client id (the application (Client) identifier in Authway) is still required and is passed in the body.
  • code_verifier: The original string generated by the client before the authorization request.
    Authway verifies this value by re-computing the code challenge and comparing it with the one sent earlier. If they match, the token exchange succeeds.

Creating the Code Verifier and Code Challenge

Please use a tested and documented client library instead of relying on this code, which should only be used for extended understanding of how the PKCE flow works.

Before initiating the authorization request, the application must generate a code verifier and derive a code challenge from it.

  • code_verifier: A high-entropy cryptographic random string (43–128 characters) consisting of letters, digits, and the characters -._~.

  • code_challenge: A value derived from the code_verifier, created by applying the SHA256 hash function and then Base64URL-encoding the result.

Here is an example of how to create these values in Node.js:

import crypto from "crypto";

// Generate a high-entropy random string as the code_verifier
const codeVerifier = crypto.randomBytes(64)
  .toString("base64")
  .replace(/\+/g, "-")
  .replace(/\//g, "_")
  .replace(/=+$/, "");

// Create the SHA256 hash of the code_verifier
const hash = crypto.createHash("sha256").update(codeVerifier).digest();

// Base64URL-encode the hash to get the code_challenge
const codeChallenge = hash
  .toString("base64")
  .replace(/\+/g, "-")
  .replace(/\//g, "_")
  .replace(/=+$/, "");

console.log("Code Verifier:", codeVerifier);
console.log("Code Challenge:", codeChallenge);

The code_verifier must be securely stored by the application (for example, in the session or local storage) so that it can later be included in the token request.