Skip to content

feat(oauth): add dynamic client registration endpoint (RFC 7591)#103

Merged
appleboy merged 4 commits intomainfrom
feat/dynamic-client-registration-rfc7591
Mar 14, 2026
Merged

feat(oauth): add dynamic client registration endpoint (RFC 7591)#103
appleboy merged 4 commits intomainfrom
feat/dynamic-client-registration-rfc7591

Conversation

@appleboy
Copy link
Copy Markdown
Member

Summary

  • Add POST /oauth/register endpoint implementing RFC 7591 Dynamic Client Registration
  • Gated behind ENABLE_DYNAMIC_CLIENT_REGISTRATION=true env var (disabled by default for security)
  • Registered clients start in pending status and require admin approval before use
  • Supports grant_types: authorization_code, device_code (including URN form)
  • Supports token_endpoint_auth_method: none (public), client_secret_basic, client_secret_post (confidential)
  • Restricts scopes to user-safe set: email, profile, openid, offline_access
  • Rate limited at 5 req/min (configurable via DYNAMIC_CLIENT_REGISTRATION_RATE_LIMIT)
  • Returns RFC 7591 §3.2.1 compliant response with client_id, client_secret, grant_types, etc.

Configuration

# Enable dynamic client registration (default: false)
ENABLE_DYNAMIC_CLIENT_REGISTRATION=true

# Rate limit for /oauth/register (default: 5 requests/minute)
DYNAMIC_CLIENT_REGISTRATION_RATE_LIMIT=5

Example Usage

curl -X POST http://localhost:8080/oauth/register   -H "Content-Type: application/json"   -d '{
    "client_name": "My CLI Tool",
    "grant_types": ["device_code"],
    "scope": "email profile"
  }'

Test plan

  • All existing tests pass (go test ./...)
  • Linting passes (make lint — 0 issues)
  • 13 handler tests: minimal registration, device code grant, confidential client, scopes, disabled endpoint, missing fields, unsupported grant types/auth methods/scopes, invalid redirect URIs, invalid JSON, multiple grant types
  • Manual test with curl against running server

🤖 Generated with Claude Code

- Add POST /oauth/register for programmatic OAuth client registration
- Gate behind ENABLE_DYNAMIC_CLIENT_REGISTRATION env var (default: false)
- Registered clients start in "pending" status requiring admin approval
- Support grant_types: authorization_code, device_code
- Support token_endpoint_auth_method: none, client_secret_basic, client_secret_post
- Restrict scopes to user-safe set: email, profile, openid, offline_access
- Add rate limiting for registration endpoint (default: 5 req/min)
- Add EventClientRegistered audit event type
- Add 13 handler tests covering success and error paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 13, 2026 15:49
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 91.66667% with 14 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/handlers/registration.go 95.51% 5 Missing and 2 partials ⚠️
internal/bootstrap/handlers.go 0.00% 6 Missing ⚠️
internal/bootstrap/router.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an OAuth 2.0 Dynamic Client Registration endpoint (POST /oauth/register) intended to follow RFC 7591, wiring it into routing, configuration, rate limiting, and adding handler-level tests.

Changes:

  • Introduces RegistrationHandler with request validation and RFC 7591-style JSON responses.
  • Wires /oauth/register into the router and rate-limiting middleware, plus new config knobs.
  • Adds handler tests covering success and error cases; adds an audit log event constant.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/models/audit_log.go Adds a new audit event constant for dynamic registration.
internal/handlers/registration.go Implements POST /oauth/register handler and response shaping.
internal/handlers/registration_test.go Adds test coverage for registration handler behaviors.
internal/config/config.go Adds env-backed config fields to enable registration and configure rate limit.
internal/bootstrap/router.go Registers the new /oauth/register route with rate limiting.
internal/bootstrap/ratelimit.go Adds a dedicated rate limiter middleware slot for registration.
internal/bootstrap/handlers.go Instantiates and exposes the new registration handler.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +107 to +120
// 5. Determine auth method → client type
var clientType string
switch req.TokenEPAuth {
case "", "none":
clientType = services.ClientTypePublic
case "client_secret_basic", "client_secret_post":
clientType = services.ClientTypeConfidential
default:
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_client_metadata",
"error_description": "Unsupported token_endpoint_auth_method: " + req.TokenEPAuth + ". Supported: none, client_secret_basic, client_secret_post",
})
return
}
Comment on lines +160 to +175
// 8. Build RFC 7591 §3.2.1 response
app := resp.OAuthApplication
grantTypes := buildResponseGrantTypes(app)
authMethod := "none"
if app.ClientType == services.ClientTypeConfidential {
authMethod = "client_secret_basic"
}

c.JSON(http.StatusCreated, gin.H{
"client_id": app.ClientID,
"client_secret": resp.ClientSecretPlain,
"client_name": app.ClientName,
"redirect_uris": app.RedirectURIs,
"grant_types": grantTypes,
"token_endpoint_auth_method": authMethod,
"scope": app.Scopes,
Comment on lines +151 to +158
resp, err := h.clientService.CreateClient(c.Request.Context(), createReq)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_client_metadata",
"error_description": err.Error(),
})
return
}
appleboy and others added 3 commits March 13, 2026 23:54
- Wire AuditService into RegistrationHandler
- Log EventClientRegistered with client metadata and source IP on successful registration
- Distinguishes API dynamic registration from UI/Admin client creation in audit trail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion

- Default token_endpoint_auth_method to "client_secret_basic" per RFC 7591 §2
- Echo back the actual requested auth method instead of hardcoding
- Distinguish validation errors (400) from internal errors (500)
- Add tests for client_secret_post, explicit public client, and default auth method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…C 7591 §1.1)

- Add DYNAMIC_CLIENT_REGISTRATION_TOKEN config for initial access token
- When configured, require Authorization: Bearer <token> header
- Use constant-time comparison to prevent timing attacks
- Empty token config preserves open registration behavior
- Add 4 new tests covering valid/missing/wrong token and open mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@appleboy appleboy merged commit b5b8669 into main Mar 14, 2026
17 checks passed
@appleboy appleboy deleted the feat/dynamic-client-registration-rfc7591 branch March 14, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants