110 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			110 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
		
			Executable File
		
	
	
package oidc
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
 | 
						|
	"github.com/coreos/go-oidc/v3/oidc"
 | 
						|
)
 | 
						|
 | 
						|
// idTokenVerifier allows an ID Token to be verified against the issue and provided keys.
 | 
						|
type IDTokenVerifier interface {
 | 
						|
	Verify(context.Context, string) (*oidc.IDToken, error)
 | 
						|
}
 | 
						|
 | 
						|
// idTokenVerifier Used to verify an ID Token and extends oidc.idTokenVerifier from the underlying oidc library
 | 
						|
type idTokenVerifier struct {
 | 
						|
	verifier            *oidc.IDTokenVerifier
 | 
						|
	verificationOptions IDTokenVerificationOptions
 | 
						|
	allowedAudiences    map[string]struct{}
 | 
						|
}
 | 
						|
 | 
						|
// IDTokenVerificationOptions options for the oidc.idTokenVerifier that are required to verify an ID Token
 | 
						|
type IDTokenVerificationOptions struct {
 | 
						|
	AudienceClaims []string
 | 
						|
	ClientID       string
 | 
						|
	ExtraAudiences []string
 | 
						|
}
 | 
						|
 | 
						|
// NewVerifier constructs a new idTokenVerifier
 | 
						|
func NewVerifier(iv *oidc.IDTokenVerifier, vo IDTokenVerificationOptions) IDTokenVerifier {
 | 
						|
	allowedAudiences := make(map[string]struct{})
 | 
						|
	allowedAudiences[vo.ClientID] = struct{}{}
 | 
						|
	for _, extraAudience := range vo.ExtraAudiences {
 | 
						|
		allowedAudiences[extraAudience] = struct{}{}
 | 
						|
	}
 | 
						|
	return &idTokenVerifier{
 | 
						|
		verifier:            iv,
 | 
						|
		verificationOptions: vo,
 | 
						|
		allowedAudiences:    allowedAudiences,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Verify verifies incoming ID Token
 | 
						|
func (v *idTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {
 | 
						|
	token, err := v.verifier.Verify(ctx, rawIDToken)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to verify token: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	claims := map[string]interface{}{}
 | 
						|
	if err := token.Claims(&claims); err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to parse default id_token claims: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if isValidAudience, err := v.verifyAudience(token, claims); !isValidAudience {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return token, err
 | 
						|
}
 | 
						|
 | 
						|
func (v *idTokenVerifier) verifyAudience(token *oidc.IDToken, claims map[string]interface{}) (bool, error) {
 | 
						|
	for _, audienceClaim := range v.verificationOptions.AudienceClaims {
 | 
						|
		if audienceClaimValue, audienceClaimExists := claims[audienceClaim]; audienceClaimExists {
 | 
						|
 | 
						|
			// audience claim value can be either interface{} or []interface{},
 | 
						|
			// as per spec `aud` can be either a string or a list of strings
 | 
						|
			switch audienceClaimValueType := audienceClaimValue.(type) {
 | 
						|
			case []interface{}:
 | 
						|
				token.Audience = v.interfaceSliceToString(audienceClaimValue)
 | 
						|
			case interface{}:
 | 
						|
				token.Audience = []string{audienceClaimValue.(string)}
 | 
						|
			default:
 | 
						|
				return false, fmt.Errorf("audience claim %s holds unsupported type %T",
 | 
						|
					audienceClaim, audienceClaimValueType)
 | 
						|
			}
 | 
						|
 | 
						|
			return v.isValidAudience(audienceClaim, token.Audience, v.allowedAudiences)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false, fmt.Errorf("audience claims %v do not exist in claims: %v",
 | 
						|
		v.verificationOptions.AudienceClaims, claims)
 | 
						|
}
 | 
						|
 | 
						|
func (v *idTokenVerifier) isValidAudience(claim string, audience []string, allowedAudiences map[string]struct{}) (bool, error) {
 | 
						|
	for _, aud := range audience {
 | 
						|
		if _, allowedAudienceExists := allowedAudiences[aud]; allowedAudienceExists {
 | 
						|
			return true, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false, fmt.Errorf(
 | 
						|
		"audience from claim %s with value %s does not match with any of allowed audiences %v",
 | 
						|
		claim, audience, allowedAudiences)
 | 
						|
}
 | 
						|
 | 
						|
func (v *idTokenVerifier) interfaceSliceToString(slice interface{}) []string {
 | 
						|
	s := reflect.ValueOf(slice)
 | 
						|
	if s.Kind() != reflect.Slice {
 | 
						|
		panic(fmt.Sprintf("given a non-slice type %s", s.Kind()))
 | 
						|
	}
 | 
						|
	var strings []string
 | 
						|
	for i := 0; i < s.Len(); i++ {
 | 
						|
		strings = append(strings, s.Index(i).Interface().(string))
 | 
						|
	}
 | 
						|
	return strings
 | 
						|
}
 |