Files
pocket-id/backend/internal/oidc/token_handler.go
2026-06-22 18:42:02 +02:00

78 lines
2.6 KiB
Go

package oidc
import (
"log/slog"
"github.com/gin-gonic/gin"
"github.com/ory/fosite"
)
type tokenHandler struct {
provider fosite.OAuth2Provider
claimsService *ClaimsService
}
func newTokenHandler(provider fosite.OAuth2Provider, claimsService *ClaimsService) *tokenHandler {
return &tokenHandler{
provider: provider,
claimsService: claimsService,
}
}
func (h *tokenHandler) token(c *gin.Context) {
ctx := c.Request.Context()
// For grants that continue an existing session (authorization code, refresh token),
// fosite restores the stored session over this empty one.
session := NewEmptySession()
accessRequest, err := h.provider.NewAccessRequest(ctx, c.Request, session)
if err != nil {
slog.ErrorContext(ctx, "Failed to create access request", "error", err)
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
return
}
requestSession, ok := accessRequest.GetSession().(*Session)
if !ok {
slog.ErrorContext(ctx, "Failed to handle token request: session must be *oidc.Session")
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, fosite.ErrServerError)
return
}
if client, ok := accessRequest.GetClient().(Client); ok {
// Re-validate the resource owner on every user-bound grant.
if err := h.claimsService.ValidateUserAccess(ctx, requestSession.Subject, client); err != nil {
slog.WarnContext(ctx, "Rejected token request: user no longer allowed to access client", "error", err.Error())
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
return
}
// Bind every issued JWT access token to the requesting client so it always carries an aud claim.
accessRequest.GrantAudience(client.GetID())
}
if err := h.claimsService.applyIDTokenClaims(ctx, requestSession, accessRequest.GetGrantedScopes()); err != nil {
slog.ErrorContext(ctx, "Failed to apply ID token claims", "error", err)
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
return
}
// The client credentials grant has no resource owner, so no subject is ever set. Assign a
// stable synthetic subject so the issued JWT access token still carries a subclaim.
if requestSession.Subject == "" {
if client, ok := accessRequest.GetClient().(Client); ok && accessRequest.GetGrantTypes().Has(string(fosite.GrantTypeClientCredentials)) {
requestSession.Subject = "client-" + client.GetID()
}
}
response, err := h.provider.NewAccessResponse(ctx, accessRequest)
if err != nil {
slog.ErrorContext(ctx, "Failed to create access response", "error", err)
h.provider.WriteAccessError(ctx, c.Writer, accessRequest, err)
return
}
h.provider.WriteAccessResponse(ctx, c.Writer, accessRequest, response)
}