Understanding Keycloak: A Guide to Access Token, Refresh Token, and ID Token
- pranaypourkar
- Jun 2, 2023
- 8 min read
Updated: Jun 26, 2023
Authentication and Authorization are two concepts in Keycloak that governs access to resources based on the identity of users.
Authentication: Authentication in Keycloak involves verifying the identity of a user. It ensures that users are who they claim to be before granting them access to protected resources. Keycloak supports various authentication mechanisms, including username/password, social login (e.g., Google, Facebook), and multi-factor authentication.
For Example: We have a web application protected by Keycloak. When a user tries to access a secured page, the application redirects them to Keycloak for authentication. The user enters their username and password on the Keycloak login page. Keycloak verifies the credentials and, upon successful authentication, issues an access token that represents the user's identity.
Authorization: Authorization in Keycloak involves determining what resources a user can access and what actions they can perform. It defines the permissions and privileges associated with different roles and assigns those roles to users. Keycloak provides role-based access control (RBAC) to manage authorization.
For Example: After a user has been authenticated, Keycloak can enforce authorization rules based on their assigned roles. For instance, let's consider an e-commerce application where users can be assigned roles such as "customer" or "admin". A customer with the "customer" role may have permission to view products, add items to their cart, and place orders. On the other hand, an admin with the "admin" role may have additional privileges to manage products, view customer details, and perform administrative tasks. Keycloak ensures that only users with the appropriate roles can access specific resources or perform specific actions.
Keycloak uses token-based authentication, where tokens (such as Access Token, Refresh Token, and ID Token) are used to verify the identity of the client and the user. Access Token, Refresh Token, and ID Token are all used in the context of authentication and authorization, particularly in the OAuth 2.0 and OpenID Connect protocols in Keycloak.
Access Token: An access token is a credential that is issued by an authentication server such as Keycloak to a client application after successful authentication. It represents the identity of the authenticated user and contains information about the user and their granted permissions and scope. The access token is typically short-lived and has an expiration time. It is used by the client to access protected resources or make API requests on behalf of the user.
Refresh Token: A refresh token is a long-lived credential that is also issued by the authentication server during the authentication process. It is used to obtain a new access token after the current access token expires. When the access token expires, the client application can use the refresh token to request a new access token without requiring the user to re-authenticate. Refresh tokens are typically longer-lived than access tokens and are securely stored on the client side. They should be kept confidential and transmitted securely.
ID Token: An ID token is specific to the OpenID Connect protocol, an authentication layer built on top of OAuth 2.0. It is a JSON Web Token (JWT) that contains identity information about the authenticated user, such as their name, email address, and other profile information. The ID token is issued by the identity provider such as Keycloak during the authentication process and is consumed by the client application. It provides information about the authenticated user and can be used for authentication and user information verification. If we set the "openid" scope when requesting an access token from the Keycloak authorization server, we will receive an ID token along with the access token.
If we need to map different attributes, such as an account ID, between two different applications for single sign-on (SSO) purposes, we can utilize the ID token in Keycloak. We can configure attribute mappings to include additional attributes in the ID token
All the above tokens are typically encoded using a specific algorithm to ensure their integrity and security during transmission and storage. In the context of Keycloak, the tokens are encoded as JSON Web Tokens (JWTs).
Keycloak supports various algorithms for token encoding and signing, including:
Symmetric Algorithms: Keycloak supports symmetric algorithms like HMAC-SHA256 and HMAC-SHA512 for signing the tokens. Symmetric algorithms use a single shared secret key to both encrypt and decrypt data.
Asymmetric Algorithms: Keycloak also supports asymmetric algorithms like RSA and ECDSA for signing the tokens. These algorithms use a public-private key pair, where the Keycloak server holds the private key and the client application holds the public key. The public key is used for encryption, while the private key is used for decryption.
Some of the Symmetric and Asymmetric Algorithms are listed below. These algorithms represent a mix of symmetric (HMAC) and asymmetric (RSA, ECDSA) cryptographic operations. The choice of algorithm depends on factors such as security requirements, key management, and the capabilities of the client applications or systems that will validate the tokens.
Symmetric Algorithms:
HS256: HMAC-SHA256 symmetric signing algorithm
HS384: HMAC-SHA384 symmetric signing algorithm
HS512: HMAC-SHA512 symmetric signing algorithm
Asymmetric Algorithms:
RS256: RSA-SHA256 asymmetric signing algorithm
RS384: RSA-SHA384 asymmetric signing algorithm
RS512: RSA-SHA512 asymmetric signing algorithm
ES256: ECDSA-SHA256 asymmetric signing algorithm
ES384: ECDSA-SHA384 asymmetric signing algorithm
ES512: ECDSA-SHA512 asymmetric signing algorithm
The specific algorithm used for encoding and signing the tokens depends on the configuration of the Keycloak server and the chosen realm settings. We can configure the algorithm preferences in the Keycloak admin console under the realm settings and token settings.

Default Signature Algorithm parameter means default algorithm used to sign tokens for the realm. For example RS256 is selected in below screenshot.
RS256 is a widely used algorithm for signing JSON Web Tokens (JWTs) in the context of the JSON Web Signature (JWS) specification. It is part of the RSA family of algorithms, where the number "256" represents the size of the RSA key used, which is 256 bits.
In the JWT context, RS256 is responsible for signing the token, which ensures its integrity and authenticity. The process involves encoding the token's header and payload sections into a compact string format called the "Signing Input," and then applying an RSA digital signature using the private key corresponding to the RSA public key associated with the token issuer. This signature is appended to the token, forming the JWS. On the receiving side, the JWT can be validated by using the corresponding RSA public key to verify the signature. This ensures that the token has not been tampered with and originates from a trusted source
Encoding of the token's content itself is not specific to the RS256 algorithm. Encoding is performed to ensure that the token can be safely transmitted across different systems or mediums, but it is not directly related to the signing process performed by RS256. By default, Keycloak encodes the JWTs using Base64 encoding for the token's header and payload sections.

Public Key or Certificate is available in "Keys" Section.
Public Key: The public key is made available for download so that it can be used by client applications or other services to verify the authenticity and integrity of JWTs issued by Keycloak. When a JWT is signed using a private key, the corresponding public key is required to validate the signature. By providing the public key, Keycloak enables clients to verify the JWT signatures independently.
Certificate: In some scenarios, it may be more convenient or necessary to use a certificate instead of directly using the public key. The certificate includes the public key and additional information, such as the issuer, validity period, and certificate authority (CA) that issued the certificate. Providing the certificate allows clients to easily retrieve the public key and other relevant information in a standardized format.

JWT Tokens can be decoded easily on this page - https://jwt.io/
JWTs consist of three parts:
Header
Payload
Signature
The header specifies the algorithm used to sign the token, while the payload contains the token claims, such as the user's identity and granted permissions. The signature is used to verify the integrity of the token.
A decoded Access Token looks like below.
A decoded Refresh Token looks like below.
A decoded ID Token looks like below.
Let's understand some of the fields included in the token
Header Section:
Payload Section:
Let's understand how we can verify whether a token (say ID Token) is valid and not tampered.
There are different ways to parse and validate JWT Tokens
Manual Parsing and Validation: In this approach, we have to manually parse the JWT token by splitting it into its three components (header, payload, and signature) using a base64 decoding mechanism. Once split, we have to inspect the token's claims and validate the signature using the token's signing algorithm and the corresponding key. We have to write the logic by ourselves with the help of RFC 7519: JSON Web Token (JWT)
JWT Libraries: Utilize JWT libraries available in your programming language or framework. These libraries provide built-in methods to parse and validate JWT tokens, making the process easier and more robust. Libraries for different framework/language is available at JWT.IO - JSON Web Tokens Libraries
Identity Provider SDKs: Many identity providers offer SDKs that handle JWT parsing and validation as part of their authentication libraries. For example, libraries like Auth0 SDKs, Okta SDKs, or Azure AD libraries often include methods to validate JWT tokens issued by their respective identity providers.
Framework Integration: Some web frameworks have built-in support for JWT token handling and validation. These frameworks provide middleware or modules that handle the parsing, validation, and authentication of JWT tokens automatically.
Online Validation Tools: Use online JWT validation tools or libraries to perform validation checks without writing code. For example using this site - JWT.IO
We will be using Java JWT Library - Nimbus-JOSE-JWT Bitbucket and a
sample Spring Boot project to verify ID Token Signature of a Valid and Forged Token.
Fetch the certificate details using Certs endpoint (/realms/employee/protocol/openid-connect/certs) and use it to verify the signature of the JWT Tokens
Let's start the keycloak and mysql service using docker-compose.
version: "3.9"
# https://docs.docker.com/compose/compose-file/
services:
# If mysql volume is already created and need to change the initial setup,
# remove the volume and restart the container to reflect
# docker-compose down -v
mysql:
container_name: mysql
image: mysql:8.0.29
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: identity
MYSQL_USER: keycloak
MYSQL_PASSWORD: keycloak
MYSQL_ROOT_PASSWORD: root
volumes:
- mysql-data:/var/lib/mysql
# access url - http://localhost:1010/
keycloak:
image: quay.io/keycloak/keycloak:21.0
#image: jboss/keycloak (Does not support ARM 64 image)
command: ["start-dev"]
ports:
- 1010:8080
- 1011:8443
environment:
KC_HEALTH_ENABLED: true
KC_METRICS_ENABLED: true
KC_DB: mysql
KC_DB_URL: jdbc:mysql://mysql:3306/identity?useSSL=false&allowPublicKeyRetrieval=true&cacheServerConfiguration=true&createDatabaseIfNotExist=true
KC_DB_USERNAME: root
KC_DB_PASSWORD: root
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KEYCLOAK_FRONTEND_URL: http://localhost:1010/auth
volumes:
#- ./data:/opt/jboss/keycloak/standalone/data
#- ./themes:/opt/jboss/keycloak/standalone/themes
#- ./config:/opt/jboss/keycloak/standalone/configuration
- ./log:/opt/jboss/keycloak/standalone/log
depends_on:
- mysql
volumes:
mysql-data:
driver: local
networks:
default:
name: company_default
Realm settings attached below for the reference.
pom.xml (nimbus-jose-jwt dependency)
<!-- nimbus-jose-jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.30.1</version>
</dependency>Application.java
package com.company.project;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.X509CertUtils;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
@Slf4j
@SpringBootApplication
public class Application {
public static final String CERTS_ENDPOINT = "http://localhost:1010/realms/employee/protocol/openid-connect/certs";
public static final String VALID_ID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqaEtlM0RGc2ZzOWJMbjl3NVYzQUc0Sm5UMDJYMW0zaEpMbWY3dmR3SDhJIn0.eyJleHAiOjE2ODc3ODIxMDEsImlhdCI6MTY4Nzc4MTgwMSwiYXV0aF90aW1lIjowLCJqdGkiOiI0OWE0YzIwNC03NDA2LTQ2YTQtYjIwZi0xYmNlZjg0MTg5YjYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEwMTAvcmVhbG1zL2VtcGxveWVlIiwiYXVkIjoiZW1wbG95ZWUtc2VydmljZS1jbGllbnQiLCJzdWIiOiJmMzMxNGVhNy00NDE4LTRiZjctOWZhNy1jYzVkZWZkZTA5MWIiLCJ0eXAiOiJJRCIsImF6cCI6ImVtcGxveWVlLXNlcnZpY2UtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6Ijg4ZDVmNTQ5LTc5YjUtNDlkOC1iYTY5LTliNDMyNzIxNDk3ZCIsImF0X2hhc2giOiJTRVZtX2Q4Nk5oR2Y4TmVvSWxWUWdRIiwiYWNyIjoiMSIsInNpZCI6Ijg4ZDVmNTQ5LTc5YjUtNDlkOC1iYTY5LTliNDMyNzIxNDk3ZCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMSIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidXNlcjFAdGVzdC5jb20ifQ.HHdm6clyKB6Lh_iUxKyL6A2zqo3WNOOLBhjKWAeR5OvABK8zjAfAESt-wjV99Br3V5eOiGa2MB1PbMOIKwHW05-ZIDBQ_8SY8WWorIEOGS3BdZCjh-_SsXurQkZtHrKpR8268b6nRBkVT83KX_qd8BIJAA_6vgqwdb6z5Pnt9QzZAuDeDQLia1Ba0kdIua0OU1XoDVpAlxNdeSyHcjbRlbFxHx7nZQKmu3LFsAji8j-ypsp1ts06Jn9LDMhp30tgVKUH1MzpwOvIpD2jlpo6MMgAUmjV6Vy6xJBg46F8LItxXkIyvtRzkJiT4bm2Jubvlr5F2X0t6THY_T6ZopTTlQ";
public static final String FORGED_ID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImpoS2UzREZzZnM5YkxuOXc1VjNBRzRKblQwMlgxbTNoSkxtZjd2ZHdIOEkifQ.eyJleHAiOjE2ODc3ODIxMDEsImlhdCI6MTY4Nzc4MTgwMSwiYXV0aF90aW1lIjowLCJqdGkiOiI0OWE0YzIwNC03NDA2LTQ2YTQtYjIwZi0xYmNlZjg0MTg5YjYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjEwMTAvcmVhbG1zL2VtcGxveWVlIiwiYXVkIjoiZW1wbG95ZWUtc2VydmljZS1jbGllbnQiLCJzdWIiOiJmMzMxNGVhNy00NDE4LTRiZjctOWZhNy1jYzVkZWZkZTA5MWIiLCJ0eXAiOiJJRCIsImF6cCI6ImVtcGxveWVlLXNlcnZpY2UtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6Ijg4ZDVmNTQ5LTc5YjUtNDlkOC1iYTY5LTliNDMyNzIxNDk3ZCIsImF0X2hhc2giOiJTRVZtX2Q4Nk5oR2Y4TmVvSWxWUWdRIiwiYWNyIjoiMSIsInNpZCI6Ijg4ZDVmNTQ5LTc5YjUtNDlkOC1iYTY5LTliNDMyNzIxNDk3ZCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidXNlcjJAdGVzdC5jb20ifQ.KzV8RT8iumpFnPzt6J1HbQw5E-i4ldEzatHETf3bWyOs3GK_TIfMINnMY6Py5TkFnEwRRwLsDlWA2Vhyu70GbMusIZlY3YbmuioqLZ4R7QwE4wrcWjs-NgBAnEaTd-6T-hL3TKvjU9Yyn4WcsXcszgq8QzE9Udh1umDt57wQgWBGPuc8knf_1lh6bqnUmmH7gaEt8Yvw_yZrXxqBmxOiCBwMb4VxeLnvxFvZxYvXQzXPybd1Q25NT0D2loYx4P_1y2mCJSet2qhek0gfeaeJ7BUY66R6gd5fjAj1d7PkmI7YbJYlsLsrCZKZNg76MVf14f0Ck_Ts9Skd6DkW4l1XTA";
public static final String SIGNATURE_VERIFIED = "Signature verified";
public static final String SIGNATURE_INVALID = "Invalid signature";
public static final String PARSE_EXCEPTION = "Invalid Token";
public static final String EMAIL_CLAIM = "email";
public static void main(String[] args) throws ParseException, JOSEException {
SpringApplication.run(Application.class, args);
// Get the Certificate API Response
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> certsResponse = restTemplate.getForEntity(CERTS_ENDPOINT, String.class);
// Parse the String JSON response to extract the first key object
JWKSet jwkSet = JWKSet.parse(certsResponse.getBody());
RSAKey rsaKey = (RSAKey) jwkSet.getKeys().get(1);
log.info("Extracted Public Key/Certificate Details");
log.info("Key: {}", rsaKey);
// Extract the public key from the RSAKey
X509Certificate certificate = X509CertUtils.parse(rsaKey.getX509CertChain().get(0).decode());
RSAPublicKey publicKey = (RSAPublicKey) certificate.getPublicKey();
//Validate Valid ID Token
verifySignature(publicKey, VALID_ID_TOKEN);
//Validate Forged ID Token
verifySignature(publicKey, FORGED_ID_TOKEN);
}
public static void verifySignature(RSAPublicKey publicKey, String token) throws ParseException, JOSEException {
log.info("Validating Token: {}", token);
// Get the JWSVerifier with given public key
JWSVerifier verifier = new RSASSAVerifier(publicKey);
// Parse the JWT token string
SignedJWT signedJWT;
signedJWT = SignedJWT.parse(token);
// Verify the signature
if (signedJWT.verify(verifier)) {
log.info(SIGNATURE_VERIFIED);
JWTClaimsSet claims = signedJWT.getJWTClaimsSet();
// Extract Claims
log.info("Email: {}", claims.getClaim(EMAIL_CLAIM));
} else {
log.info(SIGNATURE_INVALID);
}
}
}Output

Postman Response of http://localhost:1010/realms/employee/protocol/openid-connect/certs


Thank you for taking the time to read this post. I hope that you found it informative and useful in your own development work.













Comments