Token Management
CoSec's token management layer provides a clean type hierarchy for representing and manipulating authentication tokens. The design separates the abstract token model (in cosec-api) from the concrete implementations (in cosec-core and cosec-jwt), following the project's convention of API-implementation separation.
Token Type Hierarchy
The token type hierarchy lives in the cosec-api module and forms a compositional lattice:
Token (base)
Token is the root marker interface:
interface TokenAccessToken
AccessToken extends Token with the access token string:
interface AccessToken : Token {
val accessToken: String
}RefreshToken
RefreshToken extends Token with the refresh token string:
interface RefreshToken : Token {
val refreshToken: String
}CompositeToken
CompositeToken combines both interfaces:
interface CompositeToken : AccessToken, RefreshTokenThis allows a single object to carry both tokens while remaining type-safe -- a method accepting AccessToken can receive a CompositeToken.
Principal Types
TokenIdCapable
TokenIdCapable mixes token identity into any type:
interface TokenIdCapable : Token {
val tokenId: String
}TokenPrincipal
TokenPrincipal extends CoSecPrincipal with token awareness:
interface TokenPrincipal : TokenIdCapable, CoSecPrincipalA TokenPrincipal represents an authenticated user along with the token ID used for authentication. This enables downstream code to reference the specific token (e.g., for audit logging or token revocation).
TokenTenantPrincipal
TokenTenantPrincipal adds tenant context:
interface TokenTenantPrincipal : TenantPrincipal, TokenPrincipalThis combines all three dimensions -- identity, token, and tenant -- into a single principal type for multi-tenant token-authenticated applications.
Converter and Verifier Interfaces
PrincipalConverter
PrincipalConverter is a fun interface that converts an AccessToken to a CoSecPrincipal:
fun interface PrincipalConverter {
fun toPrincipal(accessToken: AccessToken): CoSecPrincipal
}TokenConverter
TokenConverter converts a CoSecPrincipal into a CompositeToken:
interface TokenConverter {
fun toToken(principal: CoSecPrincipal): CompositeToken
fun toToken(
principal: CoSecPrincipal,
accessTokenValidity: Duration,
refreshTokenValidity: Duration
): CompositeToken
}The overload with custom durations enables per-request token validity control (e.g., shorter tokens for sensitive operations).
TokenVerifier
TokenVerifier extends PrincipalConverter with verification and refresh capabilities:
interface TokenVerifier : PrincipalConverter {
fun <T : TokenPrincipal> verify(accessToken: AccessToken): T
override fun toPrincipal(accessToken: AccessToken): CoSecPrincipal = verify(accessToken)
fun <T : TokenPrincipal> refresh(token: CompositeToken): T
}The default toPrincipal implementation delegates to verify, so any TokenVerifier is automatically a PrincipalConverter.
Architecture Diagrams
Token Type Hierarchy
classDiagram
direction TB
class Token {
<<interface>>
}
class AccessToken {
<<interface>>
+accessToken: String
}
class RefreshToken {
<<interface>>
+refreshToken: String
}
class CompositeToken {
<<interface>>
}
class TokenIdCapable {
<<interface>>
+tokenId: String
}
class TokenPrincipal {
<<interface>>
}
class TokenTenantPrincipal {
<<interface>>
}
class CoSecPrincipal {
<<interface>>
}
class TenantPrincipal {
<<interface>>
}
Token <|-- AccessToken
Token <|-- RefreshToken
AccessToken <|-- CompositeToken
RefreshToken <|-- CompositeToken
Token <|-- TokenIdCapable
TokenIdCapable <|-- TokenPrincipal
CoSecPrincipal <|-- TokenPrincipal
TokenPrincipal <|-- TokenTenantPrincipal
TenantPrincipal <|-- TokenTenantPrincipalToken Verification Flow
flowchart TD
A["Security Filter receives request"] --> B["Extract AccessToken from header"]
B --> C["TokenVerifier.verify(accessToken)"]
C --> D{"Validate signature + expiry"}
D -->|"Token expired"| E["throw TokenExpiredException"]
D -->|"Verification failed"| F["throw TokenVerificationException"]
D -->|"Valid"| G["Extract claims into TokenPrincipal"]
G --> H{"tenantId present?"}
H -->|"yes"| I["Create TokenTenantPrincipal"]
H -->|"no"| J["Create TokenPrincipal"]
I --> K["Set principal on SecurityContext"]
J --> K
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:#e6edf3Token Refresh Sequence
sequenceDiagram
autonumber
participant Client
participant TV as TokenVerifier
participant TC as TokenConverter
Client->>TV: refresh(CompositeToken)
TV->>TV: verify refresh token signature + expiry
TV->>TV: decode access token (no verify, may be expired)
TV->>TV: require(refresh.sub == access.jti)
TV-->>Client: TokenPrincipal (from access claims)
Client->>TC: toToken(TokenPrincipal)
TC-->>Client: new CompositeTokenConcrete Implementations (cosec-core)
| Class | Implements | Description |
|---|---|---|
SimpleCompositeToken | CompositeToken | Data class holding two strings |
SimpleAccessToken | AccessToken | Single access token wrapper |
SimpleTokenPrincipal | TokenPrincipal | Wraps a CoSecPrincipal with a token ID |
SimpleTokenTenantPrincipal | TokenTenantPrincipal | Wraps TokenPrincipal with Tenant context |
TokenCompositeAuthentication | Authentication | Chains CompositeAuthentication with TokenConverter for authenticateAsToken() |
TokenCompositeAuthentication
TokenCompositeAuthentication bridges authentication and token issuance:
class TokenCompositeAuthentication(
private val compositeAuthentication: CompositeAuthentication,
private val tokenConverter: TokenConverter
) : Authentication<Credentials, CoSecPrincipal>It provides an additional authenticateAsToken() method that authenticates credentials and immediately converts the resulting principal to a CompositeToken:
fun authenticateAsToken(credentials: Credentials): Mono<out CompositeToken>This is the primary entry point for login endpoints that need to return tokens directly.
References
- Token.kt:25 - Base token interface
- AccessToken.kt:25 - Access token interface
- RefreshToken.kt:25 - Refresh token interface
- CompositeToken.kt:24 - Combined access + refresh token
- TokenPrincipal.kt:27 - Token-aware principal
- TokenConverter.kt:27 - Principal-to-token converter
Related Pages
- JWT Integration - JWT-specific token implementation
- Authentication System - How tokens are produced during authentication
- Social Authentication - Social login producing token-bearing principals
- Authorization Flow - How token claims are used in authorization