Skip to content

Add refresh token support #96

Open
wistefan wants to merge 28 commits intomainfrom
ticket-34/work
Open

Add refresh token support #96
wistefan wants to merge 28 commits intomainfrom
ticket-34/work

Conversation

@wistefan
Copy link
Copy Markdown
Collaborator

Added OAuth2 refresh token support to VCVerifier's token endpoint, per RFC 6749 Section 1.5. The feature is opt-in via configuration so existing deployments are unaffected.

general-agent-4 and others added 14 commits April 30, 2026 08:02
5-step plan covering config, refresh token store/generation, OpenAPI spec
updates, HTTP handler wiring, and comprehensive tests. Also enriches
CLAUDE.md with important file paths and token flow details for step agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace in-memory refresh token cache with database-backed storage layer
- Add refresh_token table schema to database/schema.go plan
- Add RefreshTokenRepository interface and SqlRefreshTokenRepository to database/repository.go plan
- Add RefreshTokenRow model to database/models.go plan
- Add database repository tests to Step 5
- Require database connection when RefreshTokenEnabled is true (fail fast at startup)
- Ensure tokens survive restarts and support horizontal scaling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-34/plan into ticket-34/work

Reviewed-on: http://localhost:3001/general-agent-4/VCVerifier/pulls/1
Reviewed-by: wistefan <wistefan@dev-env.local>
Add RefreshTokenEnabled (bool, default false) and RefreshTokenExpiration
(int, default 2880 minutes / 48 hours) to the Verifier config struct,
along with a DefaultRefreshTokenExpirationMinutes named constant.
Add TYPE_REFRESH_TOKEN = "refresh_token" grant type constant to
common/metadata.go. Update existing config test expectations and add
a dedicated TestRefreshTokenConfigDefaults test with a YAML fixture
covering both default and explicit value parsing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ion and constants' (#2) from ticket-34/step-1 into ticket-34/work

Reviewed-on: http://localhost:3001/general-agent-4/VCVerifier/pulls/2
Reviewed-by: wistefan <wistefan@dev-env.local>
Implement Step 2 of the refresh token feature: database-backed storage
for refresh tokens with single-use semantics and token rotation.

- Add refresh_token table DDL to schema.go (PostgreSQL, MySQL, SQLite)
- Add RefreshTokenRow model with JSON serialization helpers for scopes
  and credentials
- Add RefreshTokenRepository interface and SqlRefreshTokenRepository
  with StoreRefreshToken, GetAndDeleteRefreshToken, DeleteExpiredTokens
- Add ExchangeRefreshToken, CreateRefreshToken, IsRefreshTokenEnabled
  to the Verifier interface and CredentialVerifier implementation
- Wire up refresh token repo in main.go when feature is enabled
- Add comprehensive tests for repository (SQLite), verifier methods,
  and model serialization helpers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Store full signed JWT string in RefreshTokenRow.JWTPayload instead of
  decomposed fields, so access tokens can be re-issued without
  re-applying credential inclusion configurations
- Remove erroneous TestRefreshTokenFlatClaims_True test (FlatClaims
  belongs to ScopeEntryRow, not RefreshTokenRow)
- Fix JWTPayload field documentation to accurately describe compact
  JWT serialization storage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…token store and generation logic' (#3) from ticket-34/step-2 into ticket-34/work

Reviewed-on: http://localhost:3001/general-agent-4/VCVerifier/pulls/3
Reviewed-by: wistefan <wistefan@dev-env.local>
Update TokenResponse struct with RefreshToken field (omitempty for
backward compatibility) and extend the OpenAPI spec to document the
new refresh_token grant type in TokenRequest and refresh_token field
in both TokenRequest and TokenResponse schemas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and OpenAPI spec' (#4) from ticket-34/step-3 into ticket-34/work

Reviewed-on: http://localhost:3001/general-agent-4/VCVerifier/pulls/4
Reviewed-by: wistefan <wistefan@dev-env.local>
Connect refresh token generation and exchange to the HTTP layer:

- Add grant_type=refresh_token handler that exchanges a valid refresh
  token for a new access token and rotated refresh token
- Extend GetToken (authorization_code flow) to generate and return
  refresh tokens when the feature is enabled
- Extend verifiyVPToken (vp_token and token-exchange flows) to include
  refresh tokens in responses when enabled
- Add IsRefreshTokenEnabled() and CreateRefreshToken() to Verifier
  interface, called from the openapi layer
- Store clientId in tokenStore so refresh tokens can be created during
  authorization_code exchange
- Add comprehensive tests for all refresh token paths including
  enabled/disabled states, error propagation, and all grant types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n endpoint handlers' (#5) from ticket-34/step-4 into ticket-34/work

Reviewed-on: http://localhost:3001/general-agent-4/VCVerifier/pulls/5
Reviewed-by: wistefan <wistefan@dev-env.local>
Cover all refresh token scenarios across three packages:
- database: field round-trip, token isolation, mixed-expiry cleanup,
  table-driven store/retrieve, MySQL adapt
- verifier: table-driven ExchangeRefreshToken (enabled/disabled/expired/
  error paths), multi-chain rotation, CreateRefreshToken with
  expiration timestamp and enabled/disabled, RS256 signToken, rotation
  store failure
- openapi: disabled grant types omit refresh_token, CreateRefreshToken
  failure degrades gracefully (access token still returned)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…esh token flows' (#6) from ticket-34/step-5 into ticket-34/work

Reviewed-on: http://localhost:3001/general-agent-4/VCVerifier/pulls/6
Reviewed-by: wistefan <wistefan@dev-env.local>
@wistefan wistefan added the patch Should be applied for dependency updates and small bugfixes. label Apr 30, 2026
@wistefan wistefan requested review from Mortega5 and vramperez April 30, 2026 12:49
Mortega5 added 13 commits May 4, 2026 12:52
Groups RefreshTokenEnabled and RefreshTokenExpiration into a dedicated
RefreshToken struct, accessed as verifier.refreshToken.enabled/expiration
in YAML. Defaults (enabled=false, expiration=2880) are preserved.
Tokens are now always stored as HMAC-SHA256 hex digests. The HMAC key
(salt) can be provided via refreshToken.hashSalt in server.yaml for
stability across restarts; when absent a random salt is generated at
startup, invalidating all tokens on restart.

A token_suffix column (VARCHAR 5) is added to the refresh_token table,
always storing the last 5 characters of the raw token for operational
identification regardless of hashing.
…egrity

Replace the full signed access token stored in the refresh_token table with
the raw JWT claims (base64url-decoded payload segment) to avoid persisting
a usable bearer token in the database.

Add HMAC-SHA256 integrity protection computed by the repository using the
existing hash salt, binding the raw refresh token value to its stored claims
and client ID. Tampering is detected on retrieval (after consuming the token
to prevent replay) and returns a warning log plus a 403 via the existing
ErrorRefreshTokenInvalidSignature path.
…n files

Separates SqlServiceRepository and SqlRefreshTokenRepository into their own
files within the same package to improve readability and cohesion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Should be applied for dependency updates and small bugfixes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants