Skip to content

JWT Integration

CoSec uses the Auth0 java-jwt library to create and verify JSON Web Tokens. The integration is encapsulated in the cosec-jwt module, which provides JwtTokenConverter (issue tokens) and JwtTokenVerifier (verify and extract principals). Spring Boot auto-configuration wires everything together.

Token Lifecycle

Token Validity Defaults

Token TypeDefault ValidityConfigurable Via
Access Token10 minutescosec.jwt.token-validity.access
Refresh Token7 dayscosec.jwt.token-validity.refresh

These defaults are defined in JwtProperties:

kotlin
data class TokenValidity(
    var access: Duration = Duration.ofMinutes(10),
    var refresh: Duration = Duration.ofDays(7)
)

Supported Algorithms

The auto-configuration supports three HMAC algorithms, selected via cosec.jwt.algorithm:

ValueAlgorithmJavadoc
HMAC256 (default)HS256Algorithm.HMAC256(secret)
HMAC384HS384Algorithm.HMAC384(secret)
HMAC512HS512Algorithm.HMAC512(secret)

JWT Claims Structure

JwtTokenConverter builds a JWT with the following claims structure for access tokens:

json
{
  "jti": "<generated-unique-id>",
  "sub": "<principal.id>",
  "iat": 1684000000000,
  "exp": 1684000600000,
  "policies": ["policy-id-1", "policy-id-2"],
  "roles": ["admin", "user"],
  "attributes": {"key": "value"},
  "tenantId": "tenant-123"
}

Key mappings:

  • sub (Subject): Set to principal.id -- the unique user identifier
  • jti (JWT ID): Generated by an IdGenerator (default: UUID). Used for token revocation and refresh token binding
  • policies: PolicyCapable.POLICY_KEY claim -- list of policy IDs assigned to the principal
  • roles: RoleCapable.ROLE_KEY claim -- list of role IDs
  • attributes: CoSecPrincipal::attributes.name claim -- arbitrary key-value metadata
  • tenantId: Tenant.TENANT_ID_KEY claim -- present only when the principal implements TenantCapable

Refresh tokens have a simpler structure:

json
{
  "jti": "<refresh-token-id>",
  "sub": "<access-token-id>",
  "iat": 1684000000000,
  "exp": 1685209600000
}

The refresh token's sub claim is set to the access token's jti, creating a binding between the two tokens.

Key Classes

JwtTokenConverter

JwtTokenConverter implements TokenConverter and converts a CoSecPrincipal into a CompositeToken:

kotlin
class JwtTokenConverter(
    private val idGenerator: IdGenerator,
    private val algorithm: Algorithm,
    private val accessTokenValidity: Duration = Duration.ofMinutes(10),
    private val refreshTokenValidity: Duration = Duration.ofDays(7)
) : TokenConverter

JwtTokenVerifier

JwtTokenVerifier implements TokenVerifier and provides:

  • verify(AccessToken): Validates signature, checks expiry, extracts TokenPrincipal
  • refresh(CompositeToken): Verifies the refresh token, ensures its sub matches the access token's jti, then extracts the principal from the (possibly expired) access token

Jwts Utility

Jwts provides helper functions:

  • decode(token): Strips Bearer prefix and decodes the JWT without verification
  • toPrincipal(decodedJWT): Extracts all claims and constructs a TokenPrincipal (or TokenTenantPrincipal when tenantId is present)
  • removeBearerPrefix(): String extension that removes the "Bearer " prefix if present

Architecture Diagrams

Token Creation Flow

mermaid
sequenceDiagram
    autonumber
    participant Auth as TokenCompositeAuthentication
    participant CA as CompositeAuthentication
    participant AP as AuthenticationProvider
    participant Impl as Authentication Impl
    participant Conv as JwtTokenConverter
    participant JWT as JWT.create()

    Auth->>CA: authenticate(credentials)
    CA->>AP: get(credentialsType)
    AP-->>CA: Authentication instance
    CA->>Impl: authenticate(credentials)
    Impl-->>Auth: CoSecPrincipal
    Auth->>Conv: toToken(principal)
    Conv->>JWT: create access token (sub=policies=roles=tenantId)
    JWT-->>Conv: signed access token string
    Conv->>JWT: create refresh token (sub=accessTokenId)
    JWT-->>Conv: signed refresh token string
    Conv-->>Auth: CompositeToken(accessToken, refreshToken)

Token Verification Flow

mermaid
flowchart TD
    A["Incoming AccessToken"] --> B["removeBearerPrefix()"]
    B --> C["jwtVerifier.verify(token)"]
    C --> D{"Verification result"}
    D -->|"TokenExpiredException"| E["throw TokenExpiredException"]
    D -->|"Other Exception"| F["throw TokenVerificationException"]
    D -->|"Valid DecodedJWT"| G["Jwts.toPrincipal(decodedJWT)"]
    G --> H["Extract sub, policies, roles, attributes"]
    H --> I{"tenantId claim present?"}
    I -->|"yes"| J["return TokenTenantPrincipal"]
    I -->|"no"| K["return TokenPrincipal"]

    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style C fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style D fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style F fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style G fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style H fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style I fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style J fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style K fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Refresh Token Flow

mermaid
sequenceDiagram
    autonumber
    participant Client
    participant Verifier as JwtTokenVerifier
    participant JWT as JWT.require()
    participant Jwts as Jwts

    Client->>Verifier: refresh(CompositeToken)
    Verifier->>JWT: verify(refreshToken)
    JWT-->>Verifier: DecodedJWT (refresh)
    Verifier->>Jwts: decode(accessToken) -- no verification
    Jwts-->>Verifier: DecodedJWT (access, possibly expired)
    Verifier->>Verifier: require(refresh.sub == access.jti)
    Verifier->>Jwts: toPrincipal(accessJWT)
    Jwts-->>Verifier: TokenPrincipal
    Verifier-->>Client: TokenPrincipal

Spring Boot Auto-Configuration

CoSecJwtAutoConfiguration is activated when:

  1. cosec.enabled=true (default)
  2. cosec.jwt.enabled=true (default)
  3. JwtTokenConverter is on the classpath

It registers three beans:

BeanTypePurpose
cosecTokenAlgorithmAlgorithmHMAC algorithm from config
cosecTokenConverterTokenConverterCreates JWT tokens
cosecJwtTokenVerifierTokenVerifierVerifies JWT tokens

When authentication is also enabled, it additionally registers TokenCompositeAuthentication which chains credential-based authentication with token issuance.

Configuration Example

yaml
cosec:
  jwt:
    enabled: true
    algorithm: HMAC256
    secret: your-secret-key-must-be-long-enough
    token-validity:
      access: 10m
      refresh: 7d

References

Licensed under the Apache License, Version 2.0.