...
Keycloak (keycloak): Keycloak authenticates the user and returns a JWT token with a new signing key ID.
User to Sidecar: The user requests "Module A" via the sidecar, providing the JWT token.
Sidecar Processing:
The sidecar checks the signing key ID in the JWT token.
If the signing key ID is unknown and the last JWKS refresh was over an hour ago, the sidecar fetches the new signing keys from Keycloak and updates its local cache. It then forwards the user's request to "Module A".
If the signing key ID is known, or if a JWKS refresh occurred within the last hour, the sidecar forwards the user's request to "Module A" without fetching new keys.
If the signing key ID is unknown and a JWKS refresh occurred less than one hour ago, the sidecar rejects the user's request to avoid too frequent key updates.
Module A (moduleA): "Module A" receives and processes the request forwarded by the sidecar.
Sidecar communication example
The example is based on the 2 simple modules in app-platform-minimal
- mod-notes
and mod-users
, when the user can create a note and mod-notes
will retrieve metadata from mod-users
...
Expand |
---|
title | Sidecar interaction example |
---|
|
Code Block |
---|
@startuml
title "Create a note (positive flow)"
autonumber
skinparam responseMessageBelowArrow true
participant "Sidecar (mod-notes)" as scNotes #5CCCCC
participant "Sidecar (mod-users)" as scUsers #5CCCCC
participant Keycloak as keycloak #FFD073
participant "mod-users" as modUsers #876ED7
database "mod-users-db" as usersDb #006363
participant "mod-notes" as modNotes #876ED7
database "mod-notes-db" as notesDb #006363
[-> scNotes: Create a note\n//POST /notes//
activate scNotes #5CCCCC
scNotes -> scNotes: Resolve incoming request as ingress request
note left
//If route is not found in ingress routes cache -//
//it will be considered as egress call//
end note
scNotes -> scNotes: Verify that request is not a self-request
scNotes -> scNotes: Skip parsing of system JWT,\nbecause request does not contain it
note left
//System JWT used as a backup option for//
//all module-to-module requests without specified system user//
//in module descriptor//
end note
scNotes -> scNotes: Parse user JWT\n//(validation also performed at this stage)//
alt JWKs are not cached by ""kid"" from JWT
scNotes -> keycloak: get jwk certificates\n//GET /realms/{tenantId}/protocol/openid-connect/certs//
activate keycloak #FFD073
return jwk certificates
end
alt cross-tenant requests disabled
scNotes -> scNotes: validate that ""X-Okapi-Tenant"" header value\nmatches with JWT issuer tenant
end
scNotes -> scNotes: validate that ""X-Okapi-Tenant""\nheader value is in enabled tenants cache
scNotes -> scNotes: Skip impersonation filter,\nbecause it's not required
alt required permission cache entry is missing by key\n""notes.item.post#{tenantId}#{userId}#{sessionId}#{expirationTime}""
scNotes -> keycloak: Authorize JWT using token endpoint\n//POST /realms/{tenantId}/protocol/openid-connect/certs//
note left
Request body parameters:
//- grant_type:urn: ietf:params:oauth:grant-type:uma-ticket//
//- permission: /notes#POST//
//- audience: {tenantId}-login-application//
//Request body headers://
//- Authorization: Bearer {userJwt}//
end note
activate keycloak #FFD073
return RPT token
scNotes -> scNotes: Cache user authorization data with key:\n//notes.item.post#{tenantId}#{userId}#{sessionId}#{expirationTime}//
else cache value found
scNotes -> scNotes: Verify that request authorized using cache value
end
scNotes -> scNotes: Add self-request signature
scNotes -> scNotes: Skip desired permission population,\nbecause routing entry does not contain them
scNotes -> modNotes: Forward request: //POST /notes//
note left
//Request will contain the following headers://
//- X-Okapi-User-Id: {userId}//
//- X-Okapi-Request-Id: {sidecarNotesGeneratedRequestId}//
//- X-Okapi-Url: {resolvedNotesSidecarUrl}//
//- X-Okapi-Token: {userJwt}//
//- X-Okapi-Sidecar-Signature: {sidecarNotesSelfRequestSignature}//
end note
activate modNotes #876ED7
|||
modNotes -> scNotes: Get user by id: //GET /users/{id}//
activate scNotes #876ED7
scNotes -> scNotes: Resolve incoming request\nas egress request (module-to-module)
scNotes -> scNotes: Remove self-request signature\nbecause it is not a self-request
scNotes -> scNotes: Resolve sidecar location by module id
note left
//Information comes from ""mgr-applications""//
//from bootstrap information//
end note
alt system JWT is not cached for tenant
scNotes -> keycloak: Get system JWT\n//POST /realms/{{tenant}}/protocol/openid-connect/token//
activate keycloak #FFD073
note left
//Request body parameters://
// client_id: {client_id}//
// client_secret: {client_secret}//
// grant_type: client_credentials//
//System JWT have access to all endpoint in Folio platform//
end note
return JWT
scNotes -> scNotes: Cache system JWT for ""tenantId""
else system JWT is cached
scNotes -> scNotes: Populate System JWT from cache
end
scNotes -> scUsers: Forward request: //GET /users/{id}//
note left
//Request will contain the following headers://
//- X-Okapi-User-Id: {userId}//
//- X-Okapi-Module-Id: mod-users-{modUsersVersion}//
//- X-Okapi-Request-Id: {scNotesRequestId}//
//- X-Okapi-Token: {userJwt}//
//- X-System-Token: {systemJwt}//
end note
activate scUsers #5CCCCC
scUsers -> scUsers: Resolve incoming request as ingress request
note left
//If route is not found in ingress routes cache -//
//it will be considered as egress call//
end note
scUsers -> scUsers: Verify that request is not a self-request
scUsers -> scUsers: Verify that request does not\ncontain system-generated JWT
note left
//System JWT used as a backup option for//
//all module-to-module requests without specified system user//
//in module descriptor//
end note
scUsers -> scUsers: Parse user JWT\n//(validation also performed at this stage)//
note left
//""user_id"" is extracted from JWT, if token is parsed successfully//
//it will be populated only if initial request does not contain ""X-Okapi-User-Id"" in headers//
end note
alt JWKs are not cached by ""kid"" from JWT
scUsers -> keycloak: get jwk certificates\n//GET /realms/{tenantId}/protocol/openid-connect/certs//
activate keycloak #FFD073
return jwk certificates
end
alt cross-tenant requests disabled
scUsers -> scUsers: validate that ""X-Okapi-Tenant"" header value\nmatches with JWT issuer tenant
end
scUsers -> scUsers: validate that ""X-Okapi-Tenant""\nheader value is in enabled tenants cache
scUsers -> scUsers: Skip impersonation filter,\nbecause it's not required
alt required permission cache entry is missing by key\n""users.item.get#{tenantId}#{userId}#{sessionId}#{expirationTime}""
scUsers -> keycloak: Authorize User JWT using token endpoint\n//POST /realms/{tenantId}/protocol/openid-connect/certs//
note left
Request body parameters:
//- grant_type:urn: ietf:params:oauth:grant-type:uma-ticket//
//- permission: /users/{id}#GET//
//- audience: {tenantId}-login-application//
//Request body headers://
//- Authorization: Bearer {userJwt}//
end note
activate keycloak #FFD073
return 403 Forbidden
scUsers -> scUsers: Cache user authorization data with key:\n//users.item.get#{tenantId}#{userId}#{sessionId}#{expirationTime}//
else cache value found
scUsers -> scUsers: Verify that request authorized using cache value
end
alt user JWT is not authorized
alt required permission cache entry is missing by key\n""users.item.get#{tenantId}#{sessionId}#{expirationTime}""
scUsers -> keycloak: Authorize System JWT using token endpoint\n//POST /realms/{tenantId}/protocol/openid-connect/certs//
activate keycloak #FFD073
note left
Request body parameters:
//- grant_type:urn: ietf:params:oauth:grant-type:uma-ticket//
//- permission: /users/{id}#GET//
//- audience: {tenantId}-login-application//
//Request body headers://
//- Authorization: Bearer {systemJwt}//
end note
return RPT
scUsers -> scUsers: Cache user authorization data with key:\n//users.item.get#{tenantId}#{sessionId}#{expirationTime}//
else cache value found
scUsers -> scUsers: Verify that request authorized using cache value
end
end
scUsers -> scUsers: Add self-request signature
scUsers -> scUsers: Skip desired permission population,\nbecause routing entry does not contain them
scUsers -> modUsers: Forward request: //GET /users/{id}//
note left
//Request will contain the following headers://
//- X-Okapi-User-Id: {userId}//
//- X-Okapi-Request-Id: {scNotesRequestId}/{scUsersRequestId}//
//- X-Okapi-Url: {resolvedUsersSidecarUrl}//
//- X-Okapi-Token: {userJwt}//
//- X-Okapi-Sidecar-Signature: {sidecarUsersSelfRequestSignature}//
end note
activate modUsers #876ED7
modUsers -> usersDb: Find user by id
activate usersDb #006363
return User by id
return User by id
return User by id
|||
return User by id
modNotes -> notesDb: Save a note entity
activate notesDb #006363
return Saved note
return Created note
return Created note
@enduml |
|