diff --git a/openapi/oauth/core.go b/openapi/oauth/core.go index 012f8188..2ad7b656 100644 --- a/openapi/oauth/core.go +++ b/openapi/oauth/core.go @@ -240,7 +240,7 @@ func (s *Service) RefreshToken(ctx context.Context, refreshToken string, scope . // Generate new access token with final scope expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds()) - newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn) + newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn, nil) if err != nil { return nil, &types.ErrorResponse{ Code: types.ErrorServerError, @@ -339,7 +339,7 @@ func (s *Service) RotateRefreshToken(ctx context.Context, oldToken string, reque // Generate new tokens with final scope and original subject expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds()) - newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn) + newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn, nil) if err != nil { return nil, &types.ErrorResponse{ Code: types.ErrorServerError, @@ -428,7 +428,7 @@ func (s *Service) handleAuthorizationCodeGrant(ctx context.Context, client *type // Generate and store access token with proper scope and subject expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds()) - accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn) + accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn, nil) if err != nil { return nil, &types.ErrorResponse{ Code: types.ErrorServerError, @@ -464,7 +464,7 @@ func (s *Service) handleClientCredentialsGrant(ctx context.Context, client *type // Generate and store access token with client's scope (no user subject for client credentials) expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds()) - accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, "", expiresIn) + accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, "", expiresIn, nil) if err != nil { return nil, &types.ErrorResponse{ Code: types.ErrorServerError, @@ -507,7 +507,7 @@ func (s *Service) handleRefreshTokenGrant(ctx context.Context, client *types.Cli // Generate and store new access token with proper scope and subject expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds()) - accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn) + accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn, nil) if err != nil { return nil, &types.ErrorResponse{ Code: types.ErrorServerError, diff --git a/openapi/oauth/core_test.go b/openapi/oauth/core_test.go index d19b813b..4d8c7e61 100644 --- a/openapi/oauth/core_test.go +++ b/openapi/oauth/core_test.go @@ -315,7 +315,7 @@ func TestRevoke(t *testing.T) { clientID := GetActualClientID(testClients[0].ClientID) // Store token using the new method - err := service.storeAccessToken(token, clientID, "", "", 3600) + err := service.storeAccessToken(token, clientID, "", "", 3600, nil) assert.NoError(t, err) err = service.Revoke(ctx, token, "access_token") diff --git a/openapi/oauth/providers/user/default.go b/openapi/oauth/providers/user/default.go index 71a8ebee..314c16af 100644 --- a/openapi/oauth/providers/user/default.go +++ b/openapi/oauth/providers/user/default.go @@ -128,14 +128,14 @@ var ( // DefaultTeamFields contains basic team fields DefaultTeamFields = []interface{}{ - "id", "team_id", "name", "display_name", "description", "website", "logo", + "team_id", "name", "display_name", "description", "website", "logo", "owner_id", "status", "type_id", "type", "is_verified", "verified_at", "created_at", "updated_at", } // DefaultTeamDetailFields contains all team fields including contact info and metadata DefaultTeamDetailFields = []interface{}{ - "id", "team_id", "name", "display_name", "description", "website", "logo", + "team_id", "name", "display_name", "description", "website", "logo", "owner_id", "contact_email", "contact_phone", "is_verified", "verified_at", "verified_by", "team_code", "team_code_type", "status", "type_id", "type", "address", "street_address", "city", "state_province", "postal_code", "country", "country_name", "region", "zoneinfo", @@ -144,14 +144,14 @@ var ( // DefaultMemberFields contains basic member fields DefaultMemberFields = []interface{}{ - "id", "team_id", "user_id", "member_type", "role_id", "status", + "team_id", "user_id", "member_type", "role_id", "status", "invitation_id", "invited_by", "invited_at", "joined_at", "invitation_token", "invitation_expires_at", "last_active_at", "login_count", "message", "created_at", "updated_at", } // DefaultMemberDetailFields contains all member fields including robot config and permissions DefaultMemberDetailFields = []interface{}{ - "id", "team_id", "user_id", "member_type", "role_id", "status", + "team_id", "user_id", "member_type", "role_id", "status", "robot_name", "robot_description", "robot_avatar", "robot_config", "agents", "tools", "mcp_servers", "data_access_permissions", "system_prompt", "is_active_robot", "schedule_config", "random_activity", "activity_frequency", "last_robot_activity", diff --git a/openapi/oauth/signing.go b/openapi/oauth/signing.go index 5f45551d..ec9d60f4 100644 --- a/openapi/oauth/signing.go +++ b/openapi/oauth/signing.go @@ -434,15 +434,21 @@ func (s *Service) GetKeyID() string { } // SignToken signs a token based on the configured format (jwt or opaque) -func (s *Service) SignToken(tokenType, clientID, scope, subject string, expiresIn int) (string, error) { +// extraClaims: optional extra claims to add to the token (e.g., team_id, tenant_id) +func (s *Service) SignToken(tokenType, clientID, scope, subject string, expiresIn int, extraClaims ...map[string]interface{}) (string, error) { + var claims map[string]interface{} + if len(extraClaims) > 0 { + claims = extraClaims[0] + } + switch s.config.Token.AccessTokenFormat { case "jwt": - return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn) + return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn, claims) case "opaque": return s.signOpaqueToken(tokenType, clientID, scope, subject) default: // Default to JWT if format is not specified or unknown - return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn) + return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn, claims) } } @@ -577,25 +583,32 @@ func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *t } // signJWTToken signs a JWT token using the configured signing algorithm -func (s *Service) signJWTToken(tokenType, clientID, scope, subject string, expiresIn int) (string, error) { +func (s *Service) signJWTToken(tokenType, clientID, scope, subject string, expiresIn int, extraClaims map[string]interface{}) (string, error) { if s.signingCerts == nil || s.signingCerts.SigningKey == nil { return "", fmt.Errorf("signing certificates not initialized") } now := time.Now() - claims := &types.JWTClaims{ - StandardClaims: jwt.StandardClaims{ - Issuer: s.config.IssuerURL, - Subject: subject, - Audience: clientID, - ExpiresAt: now.Add(time.Duration(expiresIn) * time.Second).Unix(), - NotBefore: now.Unix(), - IssuedAt: now.Unix(), - Id: generateJTI(), - }, - ClientID: clientID, - Scope: scope, - TokenType: tokenType, + + // Use MapClaims to support extra claims + claims := jwt.MapClaims{ + // Standard JWT claims + "iss": s.config.IssuerURL, + "sub": subject, + "aud": clientID, + "exp": now.Add(time.Duration(expiresIn) * time.Second).Unix(), + "nbf": now.Unix(), + "iat": now.Unix(), + "jti": generateJTI(), + // Custom OAuth claims + "client_id": clientID, + "scope": scope, + "token_type": tokenType, + } + + // Add extra claims if provided (e.g., team_id, tenant_id) + for key, value := range extraClaims { + claims[key] = value } // Create token with claims @@ -614,8 +627,8 @@ func (s *Service) verifyJWTToken(tokenString string) (*types.TokenClaims, error) return nil, fmt.Errorf("signing certificates not initialized") } - // Parse token with claims - token, err := jwt.ParseWithClaims(tokenString, &types.JWTClaims{}, func(token *jwt.Token) (interface{}, error) { + // Parse token with MapClaims to support extra claims + token, err := jwt.ParseWithClaims(tokenString, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) { // Validate signing method expectedMethod := getSigningMethod(s.config.Token.AccessTokenSigningAlg) if token.Method != expectedMethod { @@ -635,22 +648,75 @@ func (s *Service) verifyJWTToken(tokenString string) (*types.TokenClaims, error) } // Extract claims - jwtClaims, ok := token.Claims.(*types.JWTClaims) + mapClaims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, fmt.Errorf("invalid JWT claims type") } // Convert to TokenClaims tokenClaims := &types.TokenClaims{ - Subject: jwtClaims.Subject, - ClientID: jwtClaims.ClientID, - Scope: jwtClaims.Scope, - TokenType: jwtClaims.TokenType, - ExpiresAt: time.Unix(jwtClaims.ExpiresAt, 0), - IssuedAt: time.Unix(jwtClaims.IssuedAt, 0), - Issuer: jwtClaims.Issuer, - Audience: []string{jwtClaims.Audience}, - JTI: jwtClaims.Id, + Extra: make(map[string]interface{}), + } + + // Extract standard claims + if sub, ok := mapClaims["sub"].(string); ok { + tokenClaims.Subject = sub + } + if clientID, ok := mapClaims["client_id"].(string); ok { + tokenClaims.ClientID = clientID + } + if scope, ok := mapClaims["scope"].(string); ok { + tokenClaims.Scope = scope + } + if tokenType, ok := mapClaims["token_type"].(string); ok { + tokenClaims.TokenType = tokenType + } + if iss, ok := mapClaims["iss"].(string); ok { + tokenClaims.Issuer = iss + } + if jti, ok := mapClaims["jti"].(string); ok { + tokenClaims.JTI = jti + } + + // Extract time claims + if exp, ok := mapClaims["exp"].(float64); ok { + tokenClaims.ExpiresAt = time.Unix(int64(exp), 0) + } + if iat, ok := mapClaims["iat"].(float64); ok { + tokenClaims.IssuedAt = time.Unix(int64(iat), 0) + } + + // Extract audience + if aud, ok := mapClaims["aud"].(string); ok { + tokenClaims.Audience = []string{aud} + } else if audArray, ok := mapClaims["aud"].([]interface{}); ok { + audience := make([]string, 0, len(audArray)) + for _, a := range audArray { + if audStr, ok := a.(string); ok { + audience = append(audience, audStr) + } + } + tokenClaims.Audience = audience + } + + // Extract extended claims for multi-tenancy and team support + if teamID, ok := mapClaims["team_id"].(string); ok { + tokenClaims.TeamID = teamID + } + if tenantID, ok := mapClaims["tenant_id"].(string); ok { + tokenClaims.TenantID = tenantID + } + + // Store all extra claims for flexibility + standardClaims := map[string]bool{ + "sub": true, "client_id": true, "scope": true, "token_type": true, + "exp": true, "iat": true, "nbf": true, "iss": true, "aud": true, "jti": true, + "team_id": true, "tenant_id": true, + } + for key, value := range mapClaims { + if !standardClaims[key] { + tokenClaims.Extra[key] = value + } } return tokenClaims, nil diff --git a/openapi/oauth/token.go b/openapi/oauth/token.go index 6d2f3658..388fcca5 100644 --- a/openapi/oauth/token.go +++ b/openapi/oauth/token.go @@ -248,8 +248,13 @@ func (s *Service) ValidateTokenBinding(ctx context.Context, token string, bindin // ============================================================================ // MakeAccessToken generates a new access token with specific parameters and stores it -func (s *Service) MakeAccessToken(clientID, scope, subject string, expiresIn int) (string, error) { - return s.generateAccessTokenWithScope(clientID, scope, subject, expiresIn) +// extraClaims: optional extra claims to add to the token (e.g., team_id, tenant_id) +func (s *Service) MakeAccessToken(clientID, scope, subject string, expiresIn int, extraClaims ...map[string]interface{}) (string, error) { + var claims map[string]interface{} + if len(extraClaims) > 0 { + claims = extraClaims[0] + } + return s.generateAccessTokenWithScope(clientID, scope, subject, expiresIn, claims) } // MakeRefreshToken generates a new refresh token with specific parameters and stores it @@ -338,19 +343,27 @@ func (s *Service) validateAudience(audience string) error { // generateAccessToken generates a new access token func (s *Service) generateAccessToken(clientID string) (string, error) { expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds()) - return s.generateAccessTokenWithScope(clientID, "", "", expiresIn) + return s.generateAccessTokenWithScope(clientID, "", "", expiresIn, nil) } // generateAccessTokenWithScope generates a new access token with specific parameters and stores it -func (s *Service) generateAccessTokenWithScope(clientID, scope, subject string, expiresIn int) (string, error) { +func (s *Service) generateAccessTokenWithScope(clientID, scope, subject string, expiresIn int, extraClaims map[string]interface{}) (string, error) { // Use the new signing mechanism based on configuration - accessToken, err := s.SignToken("access_token", clientID, scope, subject, expiresIn) + var accessToken string + var err error + + if extraClaims != nil { + accessToken, err = s.SignToken("access_token", clientID, scope, subject, expiresIn, extraClaims) + } else { + accessToken, err = s.SignToken("access_token", clientID, scope, subject, expiresIn) + } + if err != nil { return "", err } - // Store access token with metadata - err = s.storeAccessToken(accessToken, clientID, scope, subject, expiresIn) + // Store access token with metadata (including extra claims) + err = s.storeAccessToken(accessToken, clientID, scope, subject, expiresIn, extraClaims) if err != nil { return "", err } @@ -359,7 +372,7 @@ func (s *Service) generateAccessTokenWithScope(clientID, scope, subject string, } // storeAccessToken stores access token with metadata and specified expiration -func (s *Service) storeAccessToken(accessToken, clientID string, scope string, subject string, expiresIn int) error { +func (s *Service) storeAccessToken(accessToken, clientID string, scope string, subject string, expiresIn int, extraClaims map[string]interface{}) error { now := time.Now() expiresAt := now.Add(time.Duration(expiresIn) * time.Second).Unix() @@ -373,6 +386,11 @@ func (s *Service) storeAccessToken(accessToken, clientID string, scope string, s "expires_at": expiresAt, } + // Add extra claims if provided (e.g., team_id, tenant_id) + for key, value := range extraClaims { + tokenData[key] = value + } + ttl := time.Duration(expiresIn) * time.Second return s.store.Set(s.accessTokenKey(accessToken), tokenData, ttl) } diff --git a/openapi/oauth/token_test.go b/openapi/oauth/token_test.go index 17838f5b..18e95fca 100644 --- a/openapi/oauth/token_test.go +++ b/openapi/oauth/token_test.go @@ -26,7 +26,7 @@ func TestIntrospect(t *testing.T) { subject := testUsers[0].UserID // Store token using the updated method with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) response, err := service.Introspect(ctx, token) @@ -49,7 +49,7 @@ func TestIntrospect(t *testing.T) { // Store expired token with negative expiresIn (already expired) expiresIn := -3600 // Expired 1 hour ago - err := service.storeAccessToken(token, clientID, scope, subject, expiresIn) + err := service.storeAccessToken(token, clientID, scope, subject, expiresIn, nil) assert.NoError(t, err) response, err := service.Introspect(ctx, token) @@ -72,7 +72,7 @@ func TestIntrospect(t *testing.T) { clientID := GetActualClientID(testClients[0].ClientID) // Store minimal token data with expiresIn parameter - err := service.storeAccessToken(token, clientID, "", "", 3600) + err := service.storeAccessToken(token, clientID, "", "", 3600, nil) assert.NoError(t, err) response, err := service.Introspect(ctx, token) @@ -91,7 +91,7 @@ func TestIntrospect(t *testing.T) { scope := "openid profile" // Store token with expiration based on config - err := service.storeAccessToken(token, clientID, scope, "", 3600) + err := service.storeAccessToken(token, clientID, scope, "", 3600, nil) assert.NoError(t, err) response, err := service.Introspect(ctx, token) @@ -119,7 +119,7 @@ func TestTokenExchange(t *testing.T) { subject := testUsers[0].UserID // Store subject token with expiresIn parameter - err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Test token exchange @@ -174,7 +174,7 @@ func TestTokenExchange(t *testing.T) { // Store expired token with negative expiresIn expiresIn := -3600 // Expired 1 hour ago - err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn, nil) assert.NoError(t, err) response, err := service.TokenExchange(ctx, subjectToken, "urn:ietf:params:oauth:token-type:access_token", "https://api.example.com", "openid profile") @@ -194,7 +194,7 @@ func TestTokenExchange(t *testing.T) { subject := testUsers[0].UserID // Store subject token with expiresIn parameter - err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Test with valid audience (should succeed since audience validation is not enforced) @@ -212,7 +212,7 @@ func TestTokenExchange(t *testing.T) { subject := testUsers[0].UserID // Store subject token with expiresIn parameter - err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Test with empty audience @@ -230,7 +230,7 @@ func TestTokenExchange(t *testing.T) { subject := testUsers[0].UserID // Store subject token with expiresIn parameter - err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Test with invalid scope (should succeed since scope validation is basic) @@ -247,7 +247,7 @@ func TestTokenExchange(t *testing.T) { // Store expired subject token with negative expiresIn expiresIn := -3600 // Expired 1 hour ago - err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn, nil) assert.NoError(t, err) response, err := service.TokenExchange(ctx, subjectToken, "urn:ietf:params:oauth:token-type:access_token", "https://api.example.com", "openid profile") @@ -266,7 +266,7 @@ func TestTokenExchange(t *testing.T) { subject := testUsers[0].UserID // Store subject token with expiresIn parameter - err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Test without audience and scope @@ -299,7 +299,7 @@ func TestValidateTokenAudience(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) result, err := service.ValidateTokenAudience(ctx, token, expectedAudience) @@ -317,7 +317,7 @@ func TestValidateTokenAudience(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) result, err := service.ValidateTokenAudience(ctx, token, expectedAudience) @@ -335,7 +335,7 @@ func TestValidateTokenAudience(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) result, err := service.ValidateTokenAudience(ctx, token, expectedAudience) @@ -354,7 +354,7 @@ func TestValidateTokenAudience(t *testing.T) { // Store expired token with negative expiresIn expiresIn := -3600 // Expired 1 hour ago - err := service.storeAccessToken(token, clientID, scope, subject, expiresIn) + err := service.storeAccessToken(token, clientID, scope, subject, expiresIn, nil) assert.NoError(t, err) result, err := service.ValidateTokenAudience(ctx, token, expectedAudience) @@ -413,7 +413,7 @@ func TestValidateTokenBinding(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) binding := &types.TokenBinding{ @@ -434,7 +434,7 @@ func TestValidateTokenBinding(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) binding := &types.TokenBinding{ @@ -455,7 +455,7 @@ func TestValidateTokenBinding(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) binding := &types.TokenBinding{ @@ -476,7 +476,7 @@ func TestValidateTokenBinding(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) binding := &types.TokenBinding{ @@ -498,7 +498,7 @@ func TestValidateTokenBinding(t *testing.T) { // Store expired token with negative expiresIn expiresIn := -3600 // Expired 1 hour ago - err := service.storeAccessToken(token, clientID, scope, subject, expiresIn) + err := service.storeAccessToken(token, clientID, scope, subject, expiresIn, nil) assert.NoError(t, err) binding := &types.TokenBinding{ @@ -670,7 +670,7 @@ func TestTokenIntegration(t *testing.T) { // Step 2: Store token data with expiresIn parameter scope := "openid profile email" subject := testUsers[0].UserID - err = service.storeAccessToken(accessToken, clientID, scope, subject, 3600) + err = service.storeAccessToken(accessToken, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Step 3: Introspect token @@ -756,7 +756,7 @@ func TestTokenEdgeCases(t *testing.T) { subject := testUsers[0].UserID // Store token with expiresIn parameter (it will handle data types correctly) - err := service.storeAccessToken(token, clientID, scope, subject, 3600) + err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Should handle gracefully @@ -774,7 +774,7 @@ func TestTokenEdgeCases(t *testing.T) { subject := testUsers[0].UserID // Store subject token with expiresIn parameter - err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600) + err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil) assert.NoError(t, err) // Very long audience diff --git a/openapi/oauth/types/types.go b/openapi/oauth/types/types.go index ddd5b06a..7be73de1 100644 --- a/openapi/oauth/types/types.go +++ b/openapi/oauth/types/types.go @@ -572,6 +572,13 @@ type TokenClaims struct { Issuer string `json:"iss,omitempty"` // Token issuer Audience []string `json:"aud,omitempty"` // Token audience JTI string `json:"jti,omitempty"` // JWT ID (for JWT tokens) + + // Extended claims for multi-tenancy and team support + TeamID string `json:"team_id,omitempty"` // Team identifier + TenantID string `json:"tenant_id,omitempty"` // Tenant identifier + + // Extra claims for flexibility + Extra map[string]interface{} `json:"-"` // Additional custom claims (not serialized directly) } // AuthorizedInfo represents authorized information @@ -581,6 +588,10 @@ type AuthorizedInfo struct { Scope string `json:"scope,omitempty"` // Access scope SessionID string `json:"session_id,omitempty"` // Session ID UserID string `json:"user_id,omitempty"` // User ID + + // Extended fields for multi-tenancy and team support + TeamID string `json:"team_id,omitempty"` // Team identifier + TenantID string `json:"tenant_id,omitempty"` // Tenant identifier } // JWTClaims represents JWT-specific claims structure @@ -589,6 +600,10 @@ type JWTClaims struct { ClientID string `json:"client_id"` // OAuth client ID Scope string `json:"scope,omitempty"` // Access scope TokenType string `json:"token_type"` // Token type + + // Extended claims for multi-tenancy and team support + TeamID string `json:"team_id,omitempty"` // Team identifier + TenantID string `json:"tenant_id,omitempty"` // Tenant identifier } // ClientConfig represents default client configuration