Skip to main content
Version: v0.13

Authorization Server

The IDK provides components for building OAuth 2.0 authorization servers, including token issuance, client management, and standards-compliant metadata endpoints.

Overview

The authorization server module supports:

  • Client registration and authentication
  • Authorization code and token endpoints
  • Token generation with configurable signing
  • Metadata endpoint (.well-known/oauth-authorization-server)
  • PKCE and DPoP validation

Server Configuration

Configure the authorization server:

val authServer = AuthorizationServerBuilder()
.issuer("https://auth.example.com")
.signingKey(signingKeyPair)
.tokenLifetime(Duration.ofHours(1))
.refreshTokenLifetime(Duration.ofDays(30))
.build()

Client Registration

Register OAuth 2.0 clients:

// Register a public client (mobile app)
val mobileClient = authServer.registerClient {
clientId = "mobile-app"
clientType = ClientType.PUBLIC
redirectUris = listOf("myapp://callback")
grantTypes = setOf(GrantType.AUTHORIZATION_CODE, GrantType.REFRESH_TOKEN)
scopes = setOf("openid", "profile", "email")
requirePkce = true
}

// Register a confidential client (server-side app)
val webClient = authServer.registerClient {
clientId = "web-app"
clientSecret = generateSecureSecret()
clientType = ClientType.CONFIDENTIAL
redirectUris = listOf("https://app.example.com/callback")
grantTypes = setOf(GrantType.AUTHORIZATION_CODE, GrantType.CLIENT_CREDENTIALS)
scopes = setOf("openid", "profile", "api:read", "api:write")
}

Authorization Endpoint

Handle authorization requests:

fun handleAuthorizationRequest(request: HttpRequest): HttpResponse {
// Parse and validate the authorization request
val authRequest = authServer.parseAuthorizationRequest(request)

// Validate client
val client = authServer.getClient(authRequest.clientId)
?: return errorResponse("invalid_client", "Unknown client")

// Validate redirect URI
if (!client.redirectUris.contains(authRequest.redirectUri)) {
return errorResponse("invalid_request", "Invalid redirect URI")
}

// Validate PKCE if required
if (client.requirePkce && authRequest.codeChallenge == null) {
return redirectError(authRequest, "invalid_request", "PKCE required")
}

// At this point, authenticate the user and get consent
// Then generate the authorization code
val authCode = authServer.generateAuthorizationCode(
clientId = authRequest.clientId,
subject = authenticatedUser.id,
scopes = approvedScopes,
redirectUri = authRequest.redirectUri,
codeChallenge = authRequest.codeChallenge,
codeChallengeMethod = authRequest.codeChallengeMethod,
nonce = authRequest.nonce
)

// Redirect back to client
val redirectUrl = buildRedirectUrl(
redirectUri = authRequest.redirectUri,
code = authCode,
state = authRequest.state
)

return HttpResponse.redirect(redirectUrl)
}

Token Endpoint

Handle token requests:

fun handleTokenRequest(request: HttpRequest): HttpResponse {
val tokenRequest = authServer.parseTokenRequest(request)

return when (tokenRequest.grantType) {
GrantType.AUTHORIZATION_CODE -> handleAuthorizationCodeGrant(tokenRequest)
GrantType.REFRESH_TOKEN -> handleRefreshTokenGrant(tokenRequest)
GrantType.CLIENT_CREDENTIALS -> handleClientCredentialsGrant(tokenRequest)
else -> errorResponse("unsupported_grant_type", "Grant type not supported")
}
}

private fun handleAuthorizationCodeGrant(request: TokenRequest): HttpResponse {
// Validate authorization code
val codeData = authServer.validateAuthorizationCode(request.code)
?: return errorResponse("invalid_grant", "Invalid authorization code")

// Validate PKCE
if (codeData.codeChallenge != null) {
if (!authServer.validateCodeVerifier(
codeVerifier = request.codeVerifier,
codeChallenge = codeData.codeChallenge,
method = codeData.codeChallengeMethod
)) {
return errorResponse("invalid_grant", "Invalid code verifier")
}
}

// Generate tokens
val tokens = authServer.issueTokens(
subject = codeData.subject,
clientId = codeData.clientId,
scopes = codeData.scopes,
nonce = codeData.nonce
)

return jsonResponse(tokens)
}

Token Generation

Generate access tokens and ID tokens:

val tokens = authServer.issueTokens(
subject = userId,
clientId = clientId,
scopes = scopes,
nonce = nonce,
additionalClaims = mapOf(
"email" to userEmail,
"name" to userName
)
)

// tokens.accessToken - JWT access token
// tokens.idToken - OIDC ID token (if openid scope)
// tokens.refreshToken - Refresh token
// tokens.expiresIn - Seconds until access token expires
// tokens.tokenType - "Bearer" or "DPoP"

Metadata Endpoint

Serve authorization server metadata:

fun handleMetadataRequest(): HttpResponse {
val metadata = authServer.getMetadata()
return jsonResponse(metadata)
}

Example metadata response:

{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/token",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"scopes_supported": ["openid", "profile", "email"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"]
}

JWKS Endpoint

Serve the JSON Web Key Set:

fun handleJwksRequest(): HttpResponse {
val jwks = authServer.getJwks()
return jsonResponse(jwks)
}