mirror of
https://github.com/hanzo-fi/go-libs.git
synced 2026-10-30 20:53:44 +00:00
200 lines
5.0 KiB
Go
200 lines
5.0 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/go-jose/go-jose/v4"
|
|
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/authn/oidc"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/types/time"
|
|
)
|
|
|
|
// VerifyTokens implement the Token Response Validation as defined in OIDC specification
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation
|
|
func VerifyTokens[C oidc.IDClaims](ctx context.Context, accessToken, idToken string, v *Verifier) (claims C, err error) {
|
|
var nilClaims C
|
|
|
|
claims, sigAlgorithm, err := VerifyIDToken[C](ctx, idToken, v)
|
|
if err != nil {
|
|
return nilClaims, err
|
|
}
|
|
if err := VerifyAccessToken(accessToken, claims.GetAccessTokenHash(), sigAlgorithm); err != nil {
|
|
return nilClaims, err
|
|
}
|
|
|
|
return claims, nil
|
|
}
|
|
|
|
// VerifyIDToken validates the id token according to
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
|
func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v *Verifier) (claims C, algorithm jose.SignatureAlgorithm, err error) {
|
|
|
|
var nilClaims C
|
|
|
|
decrypted, err := oidc.DecryptToken(token)
|
|
if err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
payload, err := oidc.ParseToken(decrypted, &claims)
|
|
if err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if err := oidc.CheckSubject(claims); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if v.Issuer != nil {
|
|
if !v.Issuer(claims.GetIssuer()) {
|
|
return nilClaims, "", oidc.ErrIssuerInvalid
|
|
}
|
|
}
|
|
|
|
if err = oidc.CheckAudience(claims, v.ClientID); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if err = oidc.CheckAuthorizedParty(claims, v.ClientID); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
sigAlgorithm, err := oidc.CheckSignature(ctx, decrypted, payload, v.SupportedSignAlgs, v.KeySet)
|
|
if err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if err = oidc.CheckExpiration(claims, v.Offset); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if err = oidc.CheckNotBeforePayload(payload, v.Offset); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT, v.Offset); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if v.Nonce != nil {
|
|
if err = oidc.CheckNonce(claims, v.Nonce(ctx)); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
}
|
|
|
|
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
|
|
if err = oidc.CheckAuthTime(claims, v.MaxAge); err != nil {
|
|
return nilClaims, "", err
|
|
}
|
|
return claims, sigAlgorithm, nil
|
|
}
|
|
|
|
// VerifyAccessToken validates the access token according to
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation
|
|
func VerifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAlgorithm) error {
|
|
if atHash == "" {
|
|
return nil
|
|
}
|
|
|
|
actual, err := oidc.ClaimHash(accessToken, sigAlgorithm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if actual != atHash {
|
|
return oidc.ErrAtHash
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewIDTokenVerifier returns a oidc.Verifier suitable for ID token verification.
|
|
func NewIDTokenVerifier(clientID string, keySet oidc.KeySet, options ...VerifierOption) *Verifier {
|
|
v := &Verifier{
|
|
ClientID: clientID,
|
|
KeySet: keySet,
|
|
Offset: time.Second,
|
|
}
|
|
|
|
for _, opts := range append(defaultOptions, options...) {
|
|
opts(v)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
type Verifier struct {
|
|
Issuer func(string) bool
|
|
MaxAgeIAT time.Duration
|
|
Offset time.Duration
|
|
ClientID string
|
|
SupportedSignAlgs []string
|
|
MaxAge time.Duration
|
|
ACR oidc.ACRVerifier
|
|
KeySet oidc.KeySet
|
|
Nonce func(ctx context.Context) string
|
|
}
|
|
|
|
var defaultOptions = []VerifierOption{
|
|
WithOffset(time.Second),
|
|
WithNonce(func(_ context.Context) string {
|
|
return ""
|
|
}),
|
|
}
|
|
|
|
type VerifierOption func(*Verifier)
|
|
|
|
func WithOffset(offset time.Duration) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.Offset = offset
|
|
}
|
|
}
|
|
|
|
func WithNonce(nonce func(ctx context.Context) string) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.Nonce = nonce
|
|
}
|
|
}
|
|
|
|
func WithIssuer(issuer func(string) bool) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.Issuer = issuer
|
|
}
|
|
}
|
|
|
|
// WithIssuedAtOffset mitigates the risk of iat to be in the future
|
|
// because of clock skews with the ability to add an offset to the current time
|
|
func WithIssuedAtOffset(offset time.Duration) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.Offset = offset
|
|
}
|
|
}
|
|
|
|
// WithIssuedAtMaxAge provides the ability to define the maximum duration between iat and now
|
|
func WithIssuedAtMaxAge(maxAge time.Duration) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.MaxAgeIAT = maxAge
|
|
}
|
|
}
|
|
|
|
// WithACRVerifier sets the verifier for the acr claim
|
|
func WithACRVerifier(verifier oidc.ACRVerifier) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.ACR = verifier
|
|
}
|
|
}
|
|
|
|
// WithAuthTimeMaxAge provides the ability to define the maximum duration between auth_time and now
|
|
func WithAuthTimeMaxAge(maxAge time.Duration) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.MaxAge = maxAge
|
|
}
|
|
}
|
|
|
|
// WithSupportedSigningAlgorithms overwrites the default RS256 signing algorithm
|
|
func WithSupportedSigningAlgorithms(algs ...string) VerifierOption {
|
|
return func(v *Verifier) {
|
|
v.SupportedSignAlgs = algs
|
|
}
|
|
}
|