yao/openapi/oauth.go
Max c6e1c449e1 Enhance Tai service readiness checks and OAuth device flow
- Improve health check logic in CI workflows for both HTTP and gRPC readiness of the Tai service, ensuring clearer error reporting if the service fails to start.
- Update the OAuth Device Flow implementation to support additional claims during device authorization, enhancing the flexibility of the authorization process.
- Refactor the `AuthorizeDevice` method to accept extra claims, allowing for more detailed user context during authorization.
- Introduce a new utility function to extract bearer tokens from requests, streamlining token handling across the OpenAPI service.

These changes enhance the robustness of service readiness checks and improve the OAuth device authorization flow, contributing to a more reliable and flexible authentication mechanism.
2026-03-04 16:55:29 +08:00

735 lines
24 KiB
Go

package openapi
import (
"encoding/base64"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/yaoapp/yao/openapi/oauth"
"github.com/yaoapp/yao/openapi/oauth/types"
"github.com/yaoapp/yao/openapi/response"
)
// OAuth handlers
// NOTE: If using versioned paths like /v1/oauth, ensure that:
// 1. Discovery endpoints (.well-known) are at the root level, not versioned
// 2. Server metadata correctly returns versioned OAuth endpoint URLs
// 3. MCP clients are configured with the correct base URL for discovery
//
// Example setup:
// - OAuth endpoints: /v1/oauth/authorize, /v1/oauth/token, etc.
// - Discovery endpoints: /.well-known/oauth-authorization-server (root level)
// - MCP client URL: https://server.com/v1/mcp (for MCP protocol)
// - Authorization discovery: https://server.com/.well-known/oauth-authorization-server
func (openapi *OpenAPI) attachOAuth(base *gin.RouterGroup) {
// OAuth Core Endpoints (RFC 6749, OAuth 2.1)
oauth := base.Group("/oauth")
// Authorization endpoint - RFC 6749 Section 3.1
oauth.GET("/authorize", openapi.oauthAuthorize)
oauth.POST("/authorize", openapi.oauthAuthorize) // Support both GET and POST
// Token endpoint - RFC 6749 Section 3.2
oauth.POST("/token", openapi.oauthToken)
// Token revocation endpoint - RFC 7009
oauth.POST("/revoke", openapi.oauthRevoke)
// Token introspection endpoint - RFC 7662
oauth.POST("/introspect", openapi.oauthIntrospect)
// JSON Web Key Set endpoint - RFC 7517
oauth.GET("/jwks", openapi.oauthJWKS)
// UserInfo endpoint - OpenID Connect Core 1.0
oauth.GET("/userinfo", openapi.oauthUserInfo)
oauth.POST("/userinfo", openapi.oauthUserInfo) // Support both GET and POST
// OAuth Extended Endpoints
// Dynamic Client Registration - RFC 7591 (Required by MCP)
oauth.POST("/register", openapi.oauthRegister)
// Client Configuration - RFC 7592
oauth.GET("/register/:client_id", openapi.oauthGetClient)
oauth.PUT("/register/:client_id", openapi.oauthUpdateClient)
oauth.DELETE("/register/:client_id", openapi.oauthDeleteClient)
// Device Authorization Flow - RFC 8628
oauth.POST("/device_authorization", openapi.oauthDeviceAuthorization)
oauth.POST("/device/authorize", openapi.oauthDeviceAuthorize)
// Pushed Authorization Request - RFC 9126
oauth.POST("/par", openapi.oauthPushedAuthorizationRequest)
// Token Exchange - RFC 8693
oauth.POST("/token_exchange", openapi.oauthTokenExchange)
}
// OAuth Core Endpoints Implementation
// oauthAuthorize handles authorization requests - RFC 6749 Section 3.1
func (openapi *OpenAPI) oauthAuthorize(c *gin.Context) {
// Parse and validate authorization request
authReq, parseErr := openapi.parseAuthorizationRequest(c)
if parseErr != nil {
response.RespondWithAuthorizationError(c, authReq.RedirectURI, parseErr, authReq.State)
return
}
// Call OAuth service to process authorization request
authResp, err := openapi.OAuth.Authorize(c, authReq)
if err != nil {
// OAuth service returned an error
response.RespondWithAuthorizationError(c, authReq.RedirectURI, response.ErrServerError, authReq.State)
return
}
// Check if authorization response contains an error
if authResp.Error != "" {
// Convert OAuth service error to ErrorResponse
oauthError := &response.ErrorResponse{
Code: authResp.Error,
ErrorDescription: authResp.ErrorDescription,
}
response.RespondWithAuthorizationError(c, authReq.RedirectURI, oauthError, authReq.State)
return
}
// Success: redirect to client with authorization code
redirectURL := authReq.RedirectURI
if redirectURL != "" {
separator := "?"
if len(redirectURL) > 0 && redirectURL[len(redirectURL)-1:] == "?" {
separator = "&"
}
redirectURL += separator + "code=" + authResp.Code
if authResp.State != "" {
redirectURL += "&state=" + authResp.State
}
c.Redirect(http.StatusFound, redirectURL)
return
}
// Fallback: return JSON response if no redirect URI (should not happen with valid requests)
response.RespondWithSuccess(c, response.StatusOK, authResp)
}
// oauthToken handles token requests - RFC 6749 Section 3.2
func (openapi *OpenAPI) oauthToken(c *gin.Context) {
grantType := c.PostForm("grant_type")
// Validate grant type
if grantType == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
switch grantType {
case types.GrantTypeAuthorizationCode, types.GrantTypeClientCredentials, types.GrantTypeDeviceCode:
// Handle standard grants through OAuth.Token()
openapi.handleStandardTokenGrant(c, grantType)
case types.GrantTypeRefreshToken:
// Handle refresh token grant through OAuth.RefreshToken() - RFC 6749 Section 6
openapi.handleRefreshTokenGrant(c)
case types.GrantTypeTokenExchange:
// Handle token exchange through OAuth.TokenExchange() - RFC 8693
openapi.handleTokenExchangeGrant(c)
default:
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrUnsupportedGrantType)
}
}
// handleStandardTokenGrant handles authorization_code, client_credentials, and device_code grants
func (openapi *OpenAPI) handleStandardTokenGrant(c *gin.Context, grantType string) {
// Extract client credentials from Basic Auth header or form parameters
clientID, clientSecret := openapi.extractClientCredentials(c)
if clientID == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
return
}
// Validate client credentials using OAuth service
oauthService, ok := openapi.OAuth.(*oauth.Service)
if !ok {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
return
}
clientInfo, err := oauthService.GetClientProvider().GetClientByCredentials(c, clientID, clientSecret)
if err != nil {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
return
}
// Extract PKCE parameter
codeVerifier := c.PostForm("code_verifier")
// Extract grant-specific "code" parameter
var code string
switch grantType {
case types.GrantTypeAuthorizationCode:
code = c.PostForm("code")
redirectURI := c.PostForm("redirect_uri")
// Basic validation for authorization code grant
if code == "" || redirectURI == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// Validate that client supports authorization code grant
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeAuthorizationCode) {
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
return
}
case types.GrantTypeDeviceCode:
code = c.PostForm("device_code")
// Basic validation for device code grant
if code == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// Validate that client supports device code grant
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeDeviceCode) {
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
return
}
case types.GrantTypeClientCredentials:
// No code needed for client credentials
code = ""
// Validate that client supports client credentials grant
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeClientCredentials) {
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
return
}
}
// Call OAuth service to handle the token request
token, err := openapi.OAuth.Token(c, grantType, code, clientID, codeVerifier)
if err != nil {
// Convert OAuth service error to token error response with security headers
if oauthErr, ok := err.(*response.ErrorResponse); ok {
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
} else {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidGrant)
}
return
}
// Return successful token response with OAuth security headers (RFC 6749 Section 5.1: MUST set Cache-Control: no-store)
response.RespondWithSecureSuccess(c, response.StatusOK, token)
}
// handleRefreshTokenGrant handles refresh token requests - RFC 6749 Section 6
func (openapi *OpenAPI) handleRefreshTokenGrant(c *gin.Context) {
// Extract client credentials from Basic Auth header or form parameters
clientID, clientSecret := openapi.extractClientCredentials(c)
if clientID == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
return
}
// Validate client credentials using OAuth service
oauthService, ok := openapi.OAuth.(*oauth.Service)
if !ok {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
return
}
clientInfo, err := oauthService.GetClientProvider().GetClientByCredentials(c, clientID, clientSecret)
if err != nil {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
return
}
// Validate that client supports refresh token grant
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeRefreshToken) {
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
return
}
refreshToken := c.PostForm("refresh_token")
scope := c.PostForm("scope")
// Basic validation
if refreshToken == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// Call OAuth service to handle refresh token grant
var refreshResponse *types.RefreshTokenResponse
if scope != "" {
refreshResponse, err = openapi.OAuth.RefreshToken(c, refreshToken, scope)
} else {
refreshResponse, err = openapi.OAuth.RefreshToken(c, refreshToken)
}
if err != nil {
// Convert OAuth service error to token error response with security headers
if oauthErr, ok := err.(*response.ErrorResponse); ok {
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
} else {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidGrant)
}
return
}
// Return successful refresh token response with security headers
response.RespondWithSecureSuccess(c, response.StatusOK, refreshResponse)
}
// handleTokenExchangeGrant handles token exchange requests - RFC 8693
func (openapi *OpenAPI) handleTokenExchangeGrant(c *gin.Context) {
subjectToken := c.PostForm("subject_token")
subjectTokenType := c.PostForm("subject_token_type")
audience := c.PostForm("audience")
scope := c.PostForm("scope")
// Basic validation
if subjectToken == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// Call OAuth service to handle token exchange
exchangeResponse, err := openapi.OAuth.TokenExchange(c, subjectToken, subjectTokenType, audience, scope)
if err != nil {
// Convert OAuth service error to token error response with security headers
if oauthErr, ok := err.(*response.ErrorResponse); ok {
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
} else {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidGrant)
}
return
}
// Return successful token exchange response with security headers
response.RespondWithSecureSuccess(c, response.StatusOK, exchangeResponse)
}
// oauthRevoke handles token revocation - RFC 7009
func (openapi *OpenAPI) oauthRevoke(c *gin.Context) {
token := c.PostForm("token")
tokenTypeHint := c.PostForm("token_type_hint") // Optional hint about token type
if token == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// Call OAuth service to revoke the token
err := openapi.OAuth.Revoke(c, token, tokenTypeHint)
if err != nil {
// OAuth spec requires returning 200 even for invalid tokens to prevent information leakage
// Only return error for server errors
if oauthErr, ok := err.(*response.ErrorResponse); ok && oauthErr.Code == response.ErrServerError.Code {
response.RespondWithError(c, response.StatusInternalServerError, response.ErrServerError)
return
}
}
// RFC 7009: Return 200 OK for successful revocation (or invalid tokens)
c.Status(response.StatusOK)
}
// oauthIntrospect handles token introspection - RFC 7662
func (openapi *OpenAPI) oauthIntrospect(c *gin.Context) {
token := c.PostForm("token")
if token == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// Call OAuth service to introspect the token
introspectionResult, err := openapi.OAuth.Introspect(c, token)
if err != nil {
// Return inactive token response on error (RFC 7662) with security headers
tokenResponse := &response.TokenIntrospectionResponse{
Active: false,
}
response.RespondWithSecureSuccess(c, response.StatusOK, tokenResponse)
return
}
// Convert OAuth service response to API response format
tokenResponse := &response.TokenIntrospectionResponse{
Active: introspectionResult.Active,
Scope: introspectionResult.Scope,
ClientID: introspectionResult.ClientID,
Username: introspectionResult.Username,
TokenType: introspectionResult.TokenType,
ExpiresAt: introspectionResult.ExpiresAt,
IssuedAt: introspectionResult.IssuedAt,
Subject: introspectionResult.Subject,
Audience: introspectionResult.Audience,
}
response.RespondWithSecureSuccess(c, response.StatusOK, tokenResponse)
}
// oauthJWKS returns JSON Web Key Set - RFC 7517
func (openapi *OpenAPI) oauthJWKS(c *gin.Context) {
jwks, err := openapi.OAuth.JWKS(c)
if err != nil {
response.RespondWithError(c, response.StatusInternalServerError, response.ErrServerError)
return
}
// RFC 7517 compliance: Return JWKS directly with security headers
response.RespondWithSecureSuccess(c, response.StatusOK, jwks)
}
// oauthUserInfo returns user information - OpenID Connect Core 1.0
func (openapi *OpenAPI) oauthUserInfo(c *gin.Context) {
// Check for Bearer token in Authorization header
authHeader := c.GetHeader("Authorization")
if authHeader == "" || len(authHeader) < 7 || authHeader[:7] != "Bearer " {
response.RespondWithError(c, response.StatusUnauthorized, response.ErrInvalidToken)
return
}
// TODO: Implement user info retrieval
response.RespondWithError(c, response.StatusNotImplemented, response.ErrServerError)
}
// OAuth Extended Endpoints Implementation
// oauthRegister handles dynamic client registration - RFC 7591
func (openapi *OpenAPI) oauthRegister(c *gin.Context) {
var req response.DynamicClientRegistrationRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClientMetadata)
return
}
// Basic validation
if len(req.RedirectURIs) == 0 {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrMissingRedirectURI)
return
}
res, err := openapi.OAuth.DynamicClientRegistration(c, &req)
if err != nil {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClientMetadata)
return
}
// Return the authorization response with security headers (RFC 7591 compliant, contains client credentials)
response.RespondWithSecureSuccess(c, response.StatusCreated, res)
}
// extractClientCredentials extracts client ID and secret from Basic Auth header or form parameters
func (openapi *OpenAPI) extractClientCredentials(c *gin.Context) (clientID, clientSecret string) {
// First, try to get from HTTP Basic Auth header (RFC 6749 Section 3.2.1)
authHeader := c.GetHeader("Authorization")
if authHeader != "" && strings.HasPrefix(authHeader, "Basic ") {
// Decode Basic Auth
encoded := strings.TrimPrefix(authHeader, "Basic ")
decoded, err := base64Decode(encoded)
if err == nil {
parts := strings.SplitN(string(decoded), ":", 2)
if len(parts) == 2 {
return parts[0], parts[1]
}
}
}
// Fallback to form parameters (RFC 6749 Section 3.2.1)
clientID = c.PostForm("client_id")
clientSecret = c.PostForm("client_secret")
return clientID, clientSecret
}
// clientSupportsGrantType checks if a client supports a specific grant type
func (openapi *OpenAPI) clientSupportsGrantType(clientInfo *types.ClientInfo, grantType string) bool {
if clientInfo == nil || len(clientInfo.GrantTypes) == 0 {
return false
}
for _, supportedGrantType := range clientInfo.GrantTypes {
if supportedGrantType == grantType {
return true
}
}
return false
}
// base64Decode decodes a base64 string
func base64Decode(data string) ([]byte, error) {
return base64.StdEncoding.DecodeString(data)
}
// oauthGetClient retrieves client configuration - RFC 7592
func (openapi *OpenAPI) oauthGetClient(c *gin.Context) {
clientID := c.Param("client_id")
if clientID == "" {
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// TODO: Implement client retrieval logic
response.RespondWithError(c, response.StatusNotFound, response.ErrInvalidClient)
}
// oauthUpdateClient updates client configuration - RFC 7592
func (openapi *OpenAPI) oauthUpdateClient(c *gin.Context) {
clientID := c.Param("client_id")
if clientID == "" {
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
var req response.DynamicClientRegistrationRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidClientMetadata)
return
}
// TODO: Implement client update logic
response.RespondWithError(c, response.StatusNotImplemented, response.ErrServerError)
}
// oauthDeleteClient deletes client configuration - RFC 7592
func (openapi *OpenAPI) oauthDeleteClient(c *gin.Context) {
clientID := c.Param("client_id")
if clientID == "" {
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// TODO: Implement client deletion logic
c.Status(response.StatusNoContent)
}
// oauthDeviceAuthorization handles device authorization - RFC 8628
func (openapi *OpenAPI) oauthDeviceAuthorization(c *gin.Context) {
clientID := c.PostForm("client_id")
if clientID == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
scope := c.PostForm("scope")
oauthService := openapi.OAuth
res, err := oauthService.DeviceAuthorization(c, clientID, scope)
if err != nil {
if oauthErr, ok := err.(*response.ErrorResponse); ok {
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
} else {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
}
return
}
response.RespondWithSecureSuccess(c, response.StatusOK, res)
}
// oauthDeviceAuthorize allows an authenticated user to authorize a pending device code.
func (openapi *OpenAPI) oauthDeviceAuthorize(c *gin.Context) {
tokenStr := extractBearerToken(c)
if tokenStr == "" {
response.RespondWithSecureError(c, response.StatusUnauthorized, &response.ErrorResponse{
Code: types.ErrorInvalidGrant,
ErrorDescription: "Bearer token required",
})
return
}
svc, ok := openapi.OAuth.(*oauth.Service)
if !ok {
response.RespondWithSecureError(c, response.StatusInternalServerError, &response.ErrorResponse{
Code: types.ErrorServerError,
ErrorDescription: "OAuth service unavailable",
})
return
}
tokenClaims, err := svc.VerifyToken(tokenStr)
if err != nil || tokenClaims == nil {
response.RespondWithSecureError(c, response.StatusUnauthorized, &response.ErrorResponse{
Code: types.ErrorInvalidGrant,
ErrorDescription: "Invalid or expired token",
})
return
}
if tokenClaims.Subject == "" {
response.RespondWithSecureError(c, response.StatusUnauthorized, &response.ErrorResponse{
Code: types.ErrorInvalidGrant,
ErrorDescription: "Token has no subject",
})
return
}
extraClaims := tokenClaims.Extra
if extraClaims == nil {
extraClaims = make(map[string]interface{})
}
if tokenClaims.TeamID != "" {
extraClaims["team_id"] = tokenClaims.TeamID
}
if tokenClaims.TenantID != "" {
extraClaims["tenant_id"] = tokenClaims.TenantID
}
userCode := c.PostForm("user_code")
if userCode == "" {
userCode = c.Query("user_code")
}
if userCode == "" {
var body struct {
UserCode string `json:"user_code"`
}
if c.ShouldBindJSON(&body) == nil {
userCode = body.UserCode
}
}
if userCode == "" {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
if err := svc.AuthorizeDevice(c, userCode, tokenClaims.Subject, extraClaims); err != nil {
if oauthErr, ok := err.(*response.ErrorResponse); ok {
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
} else {
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidGrant)
}
return
}
response.RespondWithSecureSuccess(c, response.StatusOK, map[string]string{"status": "authorized"})
}
// oauthPushedAuthorizationRequest handles PAR - RFC 9126
func (openapi *OpenAPI) oauthPushedAuthorizationRequest(c *gin.Context) {
var req response.PushedAuthorizationRequest
if err := c.ShouldBind(&req); err != nil {
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// Basic validation
if req.ClientID == "" {
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// TODO: Implement PAR logic
parResponse := &response.PushedAuthorizationResponse{
RequestURI: "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2",
ExpiresIn: 60, // 60 seconds
}
response.RespondWithSuccess(c, response.StatusCreated, parResponse)
}
// oauthTokenExchange handles token exchange - RFC 8693
func (openapi *OpenAPI) oauthTokenExchange(c *gin.Context) {
grantType := c.PostForm("grant_type")
if grantType != types.GrantTypeTokenExchange {
response.RespondWithError(c, response.StatusBadRequest, response.ErrUnsupportedGrantType)
return
}
subjectToken := c.PostForm("subject_token")
if subjectToken == "" {
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
return
}
// TODO: Implement token exchange logic
exchangeResponse := &response.TokenExchangeResponse{
AccessToken: "exchanged-access-token",
IssuedTokenType: "urn:ietf:params:oauth:token-type:access_token",
TokenType: types.TokenTypeBearer,
ExpiresIn: 3600, // 1 hour
}
response.RespondWithSuccess(c, response.StatusOK, exchangeResponse)
}
// parseAuthorizationRequest parses and validates authorization request parameters
func (openapi *OpenAPI) parseAuthorizationRequest(c *gin.Context) (*types.AuthorizationRequest, *response.ErrorResponse) {
// Parse authorization request parameters from both GET (query) and POST (form) methods
authReq := &types.AuthorizationRequest{
ClientID: openapi.getParam(c, "client_id"),
ResponseType: openapi.getParam(c, "response_type"),
RedirectURI: openapi.getParam(c, "redirect_uri"),
Scope: openapi.getParam(c, "scope"),
State: openapi.getParam(c, "state"),
CodeChallenge: openapi.getParam(c, "code_challenge"),
CodeChallengeMethod: openapi.getParam(c, "code_challenge_method"),
Resource: openapi.getParam(c, "resource"),
Nonce: openapi.getParam(c, "nonce"),
}
// Basic validation
if authReq.ClientID == "" {
return authReq, response.ErrInvalidRequest
}
// Validate response_type parameter - RFC 6749 Section 3.1.1
if authReq.ResponseType == "" {
return authReq, response.ErrInvalidRequest
}
// Check supported response types
switch authReq.ResponseType {
case types.ResponseTypeCode:
// Authorization code flow - supported
case types.ResponseTypeToken:
// Implicit flow - deprecated in OAuth 2.1, return error
return authReq, response.ErrUnsupportedResponseType
default:
return authReq, response.ErrUnsupportedResponseType
}
return authReq, nil
}
// getParam gets parameter from both query string (GET) and form data (POST)
// This supports OAuth 2.0 authorization endpoint which can accept both GET and POST requests
func (openapi *OpenAPI) getParam(c *gin.Context, key string) string {
// First try to get from query parameters (GET request)
if value := c.Query(key); value != "" {
return value
}
// Then try to get from POST form data (POST request)
return c.PostForm(key)
}
// extractBearerToken reads the access token from Authorization header or cookie,
// matching the same logic as guard.getAccessToken.
func extractBearerToken(c *gin.Context) string {
if auth := c.GetHeader("Authorization"); strings.HasPrefix(auth, "Bearer ") {
return strings.TrimPrefix(auth, "Bearer ")
}
cookieName := response.GetCookieName("access_token")
if cookie, err := c.Cookie(cookieName); err == nil && cookie != "" {
return strings.TrimPrefix(cookie, "Bearer ")
}
return ""
}