Skip to content

Latest commit

 

History

History
307 lines (219 loc) · 16.3 KB

File metadata and controls

307 lines (219 loc) · 16.3 KB

App Authenticator

This project is about creating an extension for Keycloak to improve two-factor authentication (2FA) by allowing users to use an authenticator app. The goal is to make the process more user-friendly. Instead of requiring users to input Time-based One-Time Password (TOTP) codes, they can simply accept or reject login attempts through the app.

During the authentication process, a cryptographic keypair generated by the client is utilized. This keypair enhances security by ensuring the authenticity of the users during the authentication process.

The extension provides the necessary API endpoints to implement such an authenticator app.

Concept

Summary of the intended flow:

  1. Activation Token Exchange: Keycloak generates an Activation Token that is passed to the client (copy paste or scanning a QR-code)

  2. Key Pair Generation: The user's device generates a cryptographic key pair that is put into the device's secure storage. The public key is then registered with Keycloak, associating it with the respective user account. The client also generates and sends a unique authenticator id (uuid).

  3. Challenge Transmission: When a user initiates a login attempt, Keycloak sends a challenge to the user's device. This challenge can be transmitted either through a push notification triggered by the login attempt or by polling a specific endpoint on Keycloak.

  4. Challenge Signing: The user's device signs the challenge using its private key. This signed challenge is then sent back to Keycloak.

  5. Verification Process: Keycloak verifies the signature with the user's public key to ascertain the authenticity of the response. Based on this verification, Keycloak can make an informed decision to either accept or reject the login attempt.

API Documentation

Activation Token URL

This is an example of the activation token URL that is displayed on the Keycloak My Account Console as QR-Code and copy paste option.

http://192.168.2.127:8080/realms/dev/login-actions/action-token?key=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxYWEzY2FhMS00ZmEwLTQzNTUtYWE1ZC1lZTVhNzc4OTA0NGYifQ.eyJleHAiOjE3MDE0Mjg0NjQsImlhdCI6MTcwMTQyODE2NCwianRpIjoiZTlhZWEyYzQtOWM4Ny00MjBkLTg2NjctNjg0YzA5MjM0ZTA3IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMi4xMjc6ODA4MC9yZWFsbXMvZGV2IiwiYXVkIjoiaHR0cDovLzE5Mi4xNjguMi4xMjc6ODA4MC9yZWFsbXMvZGV2Iiwic3ViIjoiYjViZjYzOTYtMjFjZC00NjZjLTk2MzMtMGRlM2ExNTJiYTYzIiwidHlwIjoiYXBwLXNldHVwLWFjdGlvbi10b2tlbiIsIm5vbmNlIjoiZTlhZWEyYzQtOWM4Ny00MjBkLTg2NjctNjg0YzA5MjM0ZTA3Iiwib2FzaWQiOiIwYTE4YTM0MS01ZWE3LTRlZjgtYmRjNS1kZTdmNDY5MjcyYjEuUldfTERIazV6QjguMjk1OTViZmEtNzU4ZS00MjFiLWE4ZDMtMGFmYjQ4ZDE3MjhkIn0.XLh09uLq9Ybx_fCIcMhWcNELy9wnPaGMZ8pRusJ_b_g&client_id=account-console&tab_id=RW_LDHk5zB8

URL Parameters

Key Example Value Description
tab_id RW_LDHk5zB8 Tab Id of the User's Browser Session
client_id account-console Client ID the User is logging in
key see below Keycloak Action Token as JWT

Decoded Keycloak Action Token

Header

{
	"alg": "HS256",
	"typ": "JWT",
	"kid": "1aa3caa1-4fa0-4355-aa5d-ee5a7789044f"
}

Payload

{
	"exp": 1701428464,
	"iat": 1701428164,
	"jti": "e9aea2c4-9c87-420d-8667-684c09234e07",
	"iss": "http://192.168.2.127:8080/realms/dev",
	"aud": "http://192.168.2.127:8080/realms/dev",
	"sub": "b5bf6396-21cd-466c-9633-0de3a152ba63",
	"typ": "app-setup-action-token",
	"nonce": "e9aea2c4-9c87-420d-8667-684c09234e07",
	"oasid": "0a18a341-5ea7-4ef8-bdc5-de7f469272b1.RW_LDHk5zB8.29595bfa-758e-421b-a8d3-0afb48d1728d"
}

Signature

XLh09uLq9Ybx_fCIcMhWcNELy9wnPaGMZ8pRusJ_b_g

Error Responses

About HTTP Status Codes

  • 4xx indicate a problem caused by the client.
  • 401 Unauthorized Is returned when the client failed to prove it's identity. E.g. missing authentication information, invalid credentials or a presented JWT that failed verification.
  • 403 Forbidden is returned when the client proved it's identity but is not allowed to access the resource because of missing privileges.
  • 5xxindicate a server sided problem. Error codes from this range MAY NOT be returned if the problem was caused by the client. Any endpoint can return with this status code.

Error Object

Any error response can optionally return an error object as JSON in the body with the following shape. Since it is not possible to return a customized error on the action token endpoint, only the HTTP status code is returned without a body.

{
	"error": "some_error_type",
	"message": "details about the occurred error"
}

Authentication

Signature Tokens

The API endpoints require an authentication mechanism that leverages client-side generated keypairs. While drafts for HTTP Message Signatures exist, they are no well-established standards. draft-ietf-httpbis-message-signatures.

Instead of implementing concepts from this unfinished draft, the API uses client-side generated JSON Web Tokens (JWTs) as a form of request signature. The JWTs are signed using a private key stored on the client and transmitted in the x-signature header.

The client should use the authenticator id as the kid claim and use an asymmetric signature algorithm. The acceptable algorithms are:

  • PS512 with an RSASSA-PSS asymmetric key
  • ES512 with an EC asymmetric key

The JWT payload should contain:

  • The user id as sub claim, which the client can extract from the sub claim of the action token issued by Keycloak via the activation token URL.
  • An expiration time of approximately 30 seconds to mitigate replay attacks.
  • A UUID for the JWT in the jti claim, which can be used to implement one-time tokens. The token id should be stored on the Keycloak side at least until the token expires.
  • A typ claim similar to the Keycloak action tokens, containing a value for the corresponding endpoint or action, e.g., get-challenges.
  • Any additional request parameters that need to be signed.

Endpoints

Authenticator Setup

GET /realms/{realmId}/login-actions/action-token
Authentication
  • The Keycloak action token (JWT) that was exchanged beforehand via the activation token URL needs to be passed in the key query parameter.
  • The signature token (JWT) generated by the client in the x-signature header with claim typ = app-setup-signature-token.

Consistency Check

The signature token is not used for authentication here but rather for a consistency check to confirm that this token can be verified with the public key sent in this request

Parameters
Name In Description
realmId path The Keycloak realm ID
client_id query The Keycloak client id. This should always be account-console for the setup step. Client receives this value from the activation token URL.
tab_id query The Keycloak tab ID in the browser session where the user is setting up the authenticator. Client receives this value from the activation token URL.
key query The from Keycloak generated action token in form of a JWT. Client receives this value from the activation token URL.
authenticator_id query A unique ID to identify the authenticator.
device_os query The platform on which the authenticator app is running on. Supported values are: android and ios
public_key query The X.509 public key (e.g. as PKCS#8 base64 encoded) used to verify signatures
key_algorithm query Key algorithm of the public key
device_push_id (optional) query The platform specific ID to receive push notifications. For android this is the Firebase ID
Responses
  • 2xx Authenticator was successfully registered
  • 400 Bad Request Missing or invalid request parameters. This includes:
    • parsing of the JWT failed (invalid format)
    • request parameters are invalid (e.g. the signature or key algorithm is not supported)
  • 401 Unauthorized Verification of the JWT from the key query parameter failed in any form (expired, missing claims, invalid signature)
  • 409 Conflict The authenticator ID is already registered
  • 422 Unprocessable Entity Verification of the signature token failed with the given public_key and key_algorithm sent by the client.

Get Challenges

/realms/{realmId}/challenges
Authentication
  • The signature token (JWT) generated by the client in the x-signature header with claim typ = app-challenges-signature-token.
Parameters

Note: The authenticator id is retrieved from the kid header of the JWT.

Name In Description
realmId path The Keycloak realm ID
Responses
  • 2xx Login challenge dtos as array
  • 400 Bad Request the x-signature header has a wrong format
  • 401 Unauthorized The x-signature header is missing or verification failed
  • 412 Precondition Failed The referenced kid (authenticator ID) in the signature token does not exist
ChallengeDTO
{
	"userName": "johndoe",
	"userFirstName": "John",
	"userLastName": "Doe",
	"targetUrl": "http://192.168.2.127:8080/realms/dev/login-actions/action-token?key=eyJh...",
	"codeChallenge": "FlJj9I4WoezeR3MN...",
	"updatedTimestamp": 1701426908708,
	"ipAddress": "192.168.2.127",
	"device": "Other",
	"browser": "Firefox/120.0",
	"os": "Ubuntu",
	"osVersion": "Unknown"
}
Target URL Example

Example of the value in targetUrl

http://192.168.2.127:8080/realms/dev/login-actions/action-token?key=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxYWEzY2FhMS00ZmEwLTQzNTUtYWE1ZC1lZTVhNzc4OTA0NGYifQ.eyJleHAiOjE3MDE0MjcyMDgsImlhdCI6MTcwMTQyNjkwOCwianRpIjoiNDNmOWFmMTItZTdjMS00NWJlLTliMDUtYTc3M2ZlNDBiNjk4IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMi4xMjc6ODA4MC9yZWFsbXMvZGV2IiwiYXVkIjoiaHR0cDovLzE5Mi4xNjguMi4xMjc6ODA4MC9yZWFsbXMvZGV2Iiwic3ViIjoiYjViZjYzOTYtMjFjZC00NjZjLTk2MzMtMGRlM2ExNTJiYTYzIiwidHlwIjoiYXBwLWF1dGgtYWN0aW9uLXRva2VuIiwibm9uY2UiOiI0M2Y5YWYxMi1lN2MxLTQ1YmUtOWIwNS1hNzczZmU0MGI2OTgiLCJvYXNpZCI6IjRjZTgxYTJjLTlmYjEtNDI2Ni04NjcwLWVmZjIwNzgxNjZmMS5OSzRoRTJvLTZhMC4yOTU5NWJmYS03NThlLTQyMWItYThkMy0wYWZiNDhkMTcyOGQifQ.Nc5dQmBhkuShkLJAMgoHEDdsRWz04594AtIJgCwTICM&client_id=account-console&tab_id=NK4hE2o-6a0

Parameters

Key Example Value Description
tab_id NK4hE2o-6a0 Tab Id of the User's Browser Session
client_id account-console Client ID the User is logging in
key see below Keycloak Action Token as JWT

Decoded Keycloak Action Token

Header

{
	"alg": "HS256",
	"typ": "JWT",
	"kid": "1aa3caa1-4fa0-4355-aa5d-ee5a7789044f"
}

Payload

{
	"exp": 1701427208,
	"iat": 1701426908,
	"jti": "43f9af12-e7c1-45be-9b05-a773fe40b698",
	"iss": "http://192.168.2.127:8080/realms/dev",
	"aud": "http://192.168.2.127:8080/realms/dev",
	"sub": "b5bf6396-21cd-466c-9633-0de3a152ba63",
	"typ": "app-auth-action-token",
	"nonce": "43f9af12-e7c1-45be-9b05-a773fe40b698",
	"oasid": "4ce81a2c-9fb1-4266-8670-eff2078166f1.NK4hE2o-6a0.29595bfa-758e-421b-a8d3-0afb48d1728d"
}

Signature

Nc5dQmBhkuShkLJAMgoHEDdsRWz04594AtIJgCwTICM

Reply Challenge Endpoint

GET /realms/{realmId}/login-actions/action-token

Authentication

  • The JWT generated by Keycloak in the keyquery parameter. It was given to the client via the challenges endpoint or push notification.
  • The signature token (JWT) generated by the client in the x-signature header with claims:
    • typ: app-auth-signature-token
    • codeChallenge: The challenge value to sign

Parameters

Name In Description
realmId path The Keycloak realm ID
client_id query The Keycloak client ID. This should always be account-console for the setup step. Client receives this value from the targetUrl from the ChallengeDTO.
tab_id query The Keycloak tab ID in the browser session where the user is setting up the authenticator. Client receives this value from the targetUrl from the ChallengeDTO
key query The from Keycloak generated action token in form of a JWT. Client receives this value from the targetUrl from the ChallengeDTO.
granted query boolean that indicates of the login attempt was granted or not

Responses

  • 2xxchallenge reply was successfully processed

  • 400 Bad Request Missing or invalid request parameters. This includes:

    • parsing of the JWT failed (invalid format)
    • the x-signature header has a wrong format
  • 401 Unauthorized

    • The x-signature header is missing or signature verification failed
    • The required JWT in query parameter key is expired or verification of the JWT failed in any other form (missing claims, invalid signature)
  • 412 Precondition Failed The referenced key (authenticator ID) in the request signature does not exist

Development Notes

Refs: