Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. Keycloak (keycloak): Keycloak authenticates the user and returns a JWT token with a new signing key ID.

  2. User to Sidecar: The user requests "Module A" via the sidecar, providing the JWT token.

  3. 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.

  4. 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
titleSidecar 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