Fix critical security issue: verify JWT signature in access token validation

The validateAccessToken method was only decoding the JWT payload without
verifying the signature, allowing attackers to forge tokens. This fix:

- Adds ValidateAccessToken method to OIDCService that properly verifies
  JWT signature using RSA public key
- Validates issuer, expiration, and required claims
- Updates controller to use the secure validation method
- Removes insecure manual JWT parsing code
This commit is contained in:
Olivier Dumont
2025-12-30 12:36:30 +01:00
parent 020fcb9878
commit ef157ae9ba
2 changed files with 56 additions and 43 deletions

View File

@@ -401,48 +401,7 @@ func (controller *OIDCController) getAccessToken(c *gin.Context) string {
func (controller *OIDCController) validateAccessToken(accessToken string) (*config.UserContext, error) {
// Validate the JWT token using the OIDC service's public key
// This is a simplified validation - in production, you'd want to store
// access tokens and validate them properly, check token revocation, etc.
// For now, we'll use a helper method in the OIDC service to validate tokens
// Since we don't have a direct method, we'll parse and validate manually
// In a production system, you'd want to add a ValidateAccessToken method to the service
// Parse the JWT token
parts := strings.Split(accessToken, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid token format")
}
// Decode payload
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("failed to decode token payload: %w", err)
}
var claims map[string]interface{}
if err := json.Unmarshal(payload, &claims); err != nil {
return nil, fmt.Errorf("failed to unmarshal claims: %w", err)
}
// Extract user info from claims
username, _ := claims["sub"].(string)
if username == "" {
return nil, fmt.Errorf("missing sub claim")
}
// Extract email and name if available
email, _ := claims["email"].(string)
name, _ := claims["name"].(string)
// Create user context
userContext := &config.UserContext{
Username: username,
Email: email,
Name: name,
IsLoggedIn: true,
}
return userContext, nil
// This properly verifies the signature, issuer, and expiration
return controller.oidc.ValidateAccessToken(accessToken)
}

View File

@@ -281,6 +281,60 @@ func (oidc *OIDCService) GenerateAccessToken(userContext *config.UserContext, cl
return accessToken, nil
}
func (oidc *OIDCService) ValidateAccessToken(accessToken string) (*config.UserContext, error) {
token, err := jwt.Parse(accessToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return oidc.publicKey, nil
})
if err != nil {
return nil, fmt.Errorf("failed to parse access token: %w", err)
}
if !token.Valid {
return nil, errors.New("invalid access token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("invalid token claims")
}
// Verify issuer
iss, ok := claims["iss"].(string)
if !ok || iss != oidc.config.Issuer {
return nil, errors.New("invalid issuer")
}
// Check expiration
exp, ok := claims["exp"].(float64)
if !ok || time.Now().Unix() > int64(exp) {
return nil, errors.New("access token expired")
}
// Extract user info from claims
username, ok := claims["sub"].(string)
if !ok || username == "" {
return nil, errors.New("missing sub claim")
}
// Extract email and name if available
email, _ := claims["email"].(string)
name, _ := claims["name"].(string)
// Create user context
userContext := &config.UserContext{
Username: username,
Email: email,
Name: name,
IsLoggedIn: true,
}
return userContext, nil
}
func (oidc *OIDCService) GenerateIDToken(userContext *config.UserContext, clientID string, nonce string) (string, error) {
expiry := oidc.config.IDTokenExpiry
if expiry <= 0 {