Secure Logins and Resource Access with ZITADEL and OpenID Connect - Part 2

Dakshitha Ratnayake
Dakshitha Ratnayake

Developer Advocate

  1. 1. Introduction
  2. 2. Secure a Web Application Using Authorization Code (with PKCE)
    1. 2.1 The OIDC Flow
    2. 2.2 Register/Create a Web Application in ZITADEL
    3. 2.3 Test the Authentication and Authorization Flow
      1. 2.3.1 Call the Authorization Endpoint
      2. 2.3.2 Authenticate User and Get Authorization Code
      3. 2.3.3 Exchange the Authorization Code for the Access Token
      4. 2.3.4 Access Protected Resources Using an Access Token
    4. 2.4 Validate the ID Token
      1. 2.4.1 Obtain the ID Token
      2. 2.4.2 Validate the ID Token: Verify the Token Signature and Claims in the Application
  3. 3. Secure a Single Page Application (SPA) Using Authorization Code (with PKCE)
    1. 3.1 The OIDC Flow
    2. 3.2 Create and Test the Single Page Application
  4. 4. Closing Remarks

1. Introduction

We discussed the application types and the authentication flows that ZITADEL supports in Part 1 of this series. This post will cover how you can secure a Web Application and a Single Page Application (SPA) using the recommended Authorization Code with PKCE approach.

2. Secure a Web Application Using Authorization Code (with PKCE)

To proceed, you need to set up a couple of things in ZITADEL.

  1. If you don't have an instance yet, go ahead and create an instance as explained here.
  2. Also, create a new project by following the steps here.

Now let’s go ahead and create a web application in ZITADEL.

2.1 The OIDC Flow

The flow will be as follows:

  • The user navigates to your web application and clicks on a link or button to access protected resources. This triggers a request to the protected resource, and the OIDC flow begins.
  • The resource application server detects that the user is not currently authenticated, so it redirects the user to the ZITADEL authorization server's authorization endpoint, passing along the necessary parameters, including the client ID, redirect URI, and the request scope.
  • The user authenticates themselves by entering their login credentials on the ZITADEL login page.
  • ZITADEL verifies the user's credentials. If the credentials are valid, ZITADEL generates an authorization code and returns it to the web application via the redirect URI.
  • The web application uses the authorization code and PKCE verifier to request an access token from ZITADEL.
  • ZITADEL verifies the PKCE verifier, exchanges the authorization code for an access token, and returns them to the web application.
  • The web application can now use the access token to access the user's resources.

2.2 Register/Create a Web Application in ZITADEL

  1. After creating a project in your instance, go to applications to create an application as shown below.
Create a Web Application in ZITADEL.
  1. Add a name to your application and select the type WEB.
Create a Web Application in ZITADEL.
  1. Select PKCE.
Create a Web Application in ZITADEL.
  1. Add a redirect URI where the user will be redirected after they log in. For this example, we will use the OIDC debugger web application as the sample web application. If you want to test out your tokens and don’t have a web client, this playground (among many others) is a great tool to use.
Create a Web Application in ZITADEL.
  1. Review the details you provided and click on the Create button.
Create a Web Application in ZITADEL.
  1. You will see your client ID (no client secret because you don’t need one with the PKCE flow), which we will use later. Click on the Close button.
Create a Web Application in ZITADEL.
  1. And you will see the following screen. Click Save.
Create a Web Application in ZITADEL.

2.3 Test the Authentication and Authorization Flow

2.3.1 Call the Authorization Endpoint

To redirect the user to the ZITADEL authorization server's authorization endpoint, pass along the necessary parameters for the Authorization Code with PKCE flow, including:

  • response_type=code: Indicates that the authorization server should return an authorization code.
  • client_id: Identifies the web application to the authorization server.
  • redirect_uri: Specifies the URL to which the authorization server should redirect the user after authentication.
  • scope: Specifies the permissions the application is requesting.
  • code_challenge: A cryptographic hash of a code verifier that the client generates.
  • code_challenge_method: The method used to generate the code challenge (e.g. S256).

We will now send this GET request with the above parameters to the ZITADEL authorization endpoint via the OIDC Debugger. The OIDC debugger will behave as the client web application or relying party.

  1. Go to OIDC Debugger, where you will see the following screen:
Create a Web Application in ZITADEL.
  1. To provide the required values, follow the steps below.
  • Authorize URI: Go to the web application you created in ZITADEL and select URIs on the left menu and then copy the authorization_endpoint. Paste this on the Authorize URI field in the OIDC Debugger.
Create a Web Application in ZITADEL.
  • Redirect URI: https://oidcdebugger.com/debug

  • Client ID: Go to your application, select Configuration on the left menu, and then copy the Client ID. Paste this on the Client ID field.

Create a Web Application in ZITADEL.
  • Scope: openid
  • State: Use the generated state.
  • Nonce: Use the generated nonce.
  1. Select the following checkboxes: code and Use PKCE?
Create a Web Application in ZITADEL.

When you select Use PKCE? You will see the parameters related to PKCE. Select SHA-256. The Code Verifier and the Code Challenge will be generated for you. Note down the code verifier for later use. The Token URI is required for PKCE and will also be auto-filled for you based on the ZITADEL authorization endpoint you provided.

  1. Select query as the response mode. You will be able to see your request at the bottom.
Create a Web Application in ZITADEL.
  1. Click Send Request to send the request.

2.3.2 Authenticate User and Get Authorization Code

  1. Now the user will be redirected to ZITADEL for authentication. Enter the login name and password as shown in the following screens (multi-factor authentication was not enabled for this user).
Create a Web Application in ZITADEL.
Create a Web Application in ZITADEL.
  1. If the authentication is successful, the user will be redirected to https://oidcdebugger.com/debug. There you will see the authorization code appended to the URL. The OIDC Debugger goes the extra step of making a back-channel request to the token endpoint for us to get the access token. So, you will see the response from the token endpoint too.
Create a Web Application in ZITADEL.

2.3.3 Exchange the Authorization Code for the Access Token

Even though the OIDC Debugger already called the ZITADEL token endpoint and exchanged the authorization code for the access token for us, let’s go ahead and see how we can make that request ourselves.

  1. Note down your authorization code from the previous step. We will also need the code_verifier from previously.

  2. We are going to send a back-channel request to the token endpoint of your ZITADEL instance to exchange the authorization code for an access token. Go to your application and select URIs on the left menu and then copy the token_endpoint.

Create a Web Application in ZITADEL.
  1. The request will be sent to the token endpoint, typically with a request method of POST and the following parameters in the request body:
  • grant_type=authorization_code (indicates that the server is exchanging an authorization code for an access token)
  • code: The authorization code you received
  • client_id: Identifies the web application to the authorization server
  • redirect_uri: https://oidcdebugger.com/debug
  • code_verifier: The code verifier used to generate the code challenge earlier, which was auto-generated by the OIDC Debugger
  1. Open your terminal to send a cURL request in the following format:
curl --request POST \
--url <YOUR_TOKEN_ENDPOINT>\
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=<YOUR_AUTHORIZATION_CODE> \
--data redirect_uri=https://oidcdebugger.com/debug \
--data client_id=<YOUR_CLIENT_ID> \
--data code_verifier=<YOUR_CODE_VERIFIER>

Here's an example of what the request looks like:

curl --request POST \
--url https://instance1-nuc1q7.zitadel.cloud/oauth/v2/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=v_bhVJRTgqYWGmFCrxKnqBu_RD5rs2WoB--BvT_S5CfJeHRgbw \
--data redirect_uri=https://oidcdebugger.com/debug \
--data client_id=201081887379226881%40test \
--data code_verifier=3WI9I6J3hPKEqedZaEejJfzmHHCMVvDLVtdsdzTN6

  1. If ZITADEL validates the request and the access token is issued successfully, it will return the access token. The response from the ZITADEL token endpoint will be in the following format:
{"access_token":
"pf5nr0ZCNDeKSmCmuCm3X9D9Zgehgs9C98_YNtVxZVnsy4APWYnGkGdf1XpsuocFatK8rvc",
"token_type":"Bearer",
"expires_in":43199,
"id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjA5ODQ1MDc1NjUzNDUyOSJ9.eyJpc3MiOiJodHRwczovL2luc3RhbmNlMS1udWMxcTcueml0YWRlbC5jbG91ZCIsImF1ZCI6WyIyMDExMjQxOTgxMjU0Njk5NTNAdGVzdCIsIjIwMTEyNTE0OTgxMzA0NzU1M0B0ZXN0IiwiMjAxMDgxODg3Mzc5MjI2ODgxQHRlc3QiLCIyMDEwODc0OTkyMDY5ODM5MzdAdGVzdCIsIjIwMTA4ODEyMzgwNTk1ODQwMUB0ZXN0IiwiMjAxMTE3ODQ1MjE0NTI3NzQ1QHRlc3QiLCIyMDExMTk5NDgyNTUzMzA1NjFAdGVzdCIsIjIwMTEyMjM2MTQyMjk3MTEzN0B0ZXN0IiwiMjAxMTIzMTA3NDcyMjEyMjI1QHRlc3QiLCIyMDA5NTczMzA4Mjc3NzIxNjEiXSwiYXpwIjoiMjAxMDgxODg3Mzc5MjI2ODgxQHRlc3QiLCJhdF9oYXNoIjoiZ2R6YkdUNmc0ZDdiTFZSbmpVYjFidyIsImNfaGFzaCI6Ilp6VlpqY2ZGXzN3enVGYk10ai1LOVEiLCJhbXIiOlsicGFzc3dvcmQiLCJwd2QiXSwiZXhwIjoxNjc3MDA0MzM4LCJpYXQiOjE2NzY5NjExMzgsImF1dGhfdGltZSI6MTY3Njk2MDczMiwic3ViIjoiMjAwNTE1MzUyMjM1NDA5NjY1In0.xrJfMhitwPqJQED-TnMdv0K5rzT_HtR0rzqDd_bYbST1fWjczVosO5jVRSxNlaA5kXhaBwuIKCMLHLFZKztvCFhG2haMYs6FldB7iynBEyOUX-ZcPGiTuAfQVgqG-rWPVZj5MKHZLVwrFelKzX1TQ9dk2-avwxA9S-4-39Ai-Ac_ypbwobvBwTp056kLib6GfLzNJd_t0VxU_LtyV25WyipAlHYUTXVzgYHn1he_y1ZL4yOwPdGVIYBf5yOz_vMdfLVINtJq5G260TCCCZZo3UcPscmaet3JY5HczXAJJnsXpHpWibPgN1gw5ehaAB10ela8N4ZS-16a9ry2Wr6H-g"}

2.3.4 Access Protected Resources Using an Access Token

  1. The web application can now use the access token to access protected resources on behalf of the user, by including the token in the Authorization header of the API requests to the resource server. For example here is a possible format of the API request from the web application server to the protected resource server:
curl  -H "Authorization: Bearer pf5nr0ZCNDeKSmCmuCm3X9D9Zgehgs9C98_YNtVxZVnsy4APWYnGkGdf1XpsuocFatK8rvc" https://protected.example.com/api/protected-data/ -k -v

  1. And here’s a potential response from the protected resource server:
HTTP/1.1 200 OK
Content-Type: application/json

{
  "user_id": "12345",
  "orders": [
    {
      "order_number": "54321",
      "items": [
        {
          "name": "Inflatable unicorn horn for cats",
          "price": 9.99
        },
        {
          "name": "Giant googly eyes for your car",
          "price": 14.99
        },
        {
          "name": "Squirrel in underpants air freshener",
          "price": 4.99
        },
        {
          "name": "Selfie toaster",
          "price": 19.99
        }
      ],
      "total": 49.96
    }
  ]
}

2.4 Validate the ID Token

2.4.1 Obtain the ID Token

As discussed in the previous post, if your client application requires authentication and would like to obtain information about the authenticated person, then it could do so with an ID token. The ID Token contains information about the end user; this information tells your client application that the user is authenticated and can also give you information like their username, email address, etc.

If you want to include certain information about the user in the ID token, you will have to enable this in your configuration.

  1. Go to your application and select Token Setting on the left menu and then select the User info inside ID Token checkbox as shown below.
Create a Web Application in ZITADEL.
  1. And when you are sending your authorization request from the OIDC debugger (or via other means) add email to the scope along with openid as shown below. You can also add profile to get more information about the user. Let’s include just email for now.
Create a Web Application in ZITADEL.
  1. Retrieve the access token and ID token as we did before. Copy the ID token.
Create a Web Application in ZITADEL.
  1. You can pass the ID Token to different components of your web application, and these components can use the token to confirm that the user is authenticated and also retrieve information about them.
  • Let’s decode the ID token, which is in JWT format and is signed using private JSON Web Keys (JWK), the specification for which you can find here.
  • You can use a JWT library to decode your JWT in your web app. For now, use an online decoder, such as https://www.jstoolset.com/jwt and paste the copied ID token as shown below.
  • In the decoded section, you will be able to see the user’s email address in addition to the other decoded details.
Create a Web Application in ZITADEL.

2.4.2 Validate the ID Token: Verify the Token Signature and Claims in the Application

You can verify the access or ID token's signature in your application by matching the key that was used to sign in with one of the keys that you retrieved from your ZITADEL Server's JWK endpoint. Each public key is identified by a kid attribute, which should correspond with the kid claim in the ID token header.

  1. To get your keys from the jwks_uri, go to your application and select URLs on the left menu and then click on the discovery endpoint link as shown below.
Create a Web Application in ZITADEL.
  1. You can then see the jwks_uri as shown below:
Create a Web Application in ZITADEL.
  1. Go to the jwks_uri to see if the kid claims match. If they don’t, the signing keys may have changed. If this happens, try retrieving the keys again.
Create a Web Application in ZITADEL.
  1. You could also verify the following claims of the JWT in your application based on your requirements:
  • The iss (issuer) claim should be the ZITADEL issuer URL.
  • The aud (audience) claim should match the Client ID that you used to request the ID Token.
  • The iat (issued at time) claim indicates when this ID token was issued, expressed in Unix time.
  • The exp (expiry time) claim is the time at which this token will expire., expressed in Unix time. This time should not have already passed.
  • The nonce claim value should match the nonce value that you passed when you requested the ID token.

3. Secure a Single Page Application (SPA) Using Authorization Code (with PKCE)

Let’s secure a React-based Single Page Application (SPA) using ZITADEL. The React application will allow users to securely log in to ZITADEL using Authorization Code (with PKCE) and will display the user’s information once they log in.

3.1 The OIDC Flow

The flow will be as follows:

  • The user navigates to the React app (client) and clicks the "login" button.
  • The app initiates the authorization process by redirecting the user’s browser to the authorization endpoint of the ZITADEL instance, along with the PKCE parameters (code challenge, and code challenge method).
  • The user will see the ZITADEL login page, where the user can enter their credentials.
  • After successful authentication, ZITADEL generates an authorization code and redirects the user back to the React app.
  • The React app sends a request to the ZITADEL token endpoint, along with the authorization code and code verifier.
  • ZITADEL verifies the code verifier to ensure that the authorization request is coming from the same client that initiated it. If it's valid, ZITADEL returns an access token to the React app.
  • The React app uses the access token to make a request to the userinfo endpoint, which is protected.
  • The userinfo endpoint returns information about the logged-in user, such as their name and email address.
  • The React app displays this information to the user.
  • When the user wants to log out, the React app sends a request to the ZITADEL logout endpoint, which clears the access token and ends the user's session.
  • The user will be redirected to the login page of the app.

3.2 Create and Test the Single Page Application

You can follow this detailed step-by-step tutorial, which explains how you can set up ZITADEL to secure a Single Page Application and also includes the code for the React application with the functionality described above.

4. Closing Remarks

And with these examples, this post comes to an end. Hope you get to try out these examples with ZITADEL and the OpenID Connect Protocol. In the next part of this blog post, we will look into how ZITADEL can secure APIs with examples. By leveraging ZITADEL's secure and user-friendly authentication process, you can enhance the security and user experience of your applications.

In the meantime, do try out ZITADEL for FREE!

Thanks for reading!

Liked it? Share it!