feat: merge the verifier types

BREAKING CHANGE:

- The various verifier types are merged into a oidc.Verifir.
- oidc.Verfier became a struct with exported fields
This commit is contained in:
Tim Möhlmann 2023-03-17 16:10:42 +02:00
parent c8cf15e266
commit aad76b0d91
22 changed files with 188 additions and 343 deletions

View file

@ -64,7 +64,7 @@ type RelyingParty interface {
GetDeviceAuthorizationEndpoint() string
// IDTokenVerifier returns the verifier interface used for oidc id_token verification
IDTokenVerifier() IDTokenVerifier
IDTokenVerifier() *oidc.Verifier
// ErrorHandler returns the handler used for callback errors
ErrorHandler() func(http.ResponseWriter, *http.Request, string, string, string)
@ -88,7 +88,7 @@ type relyingParty struct {
cookieHandler *httphelper.CookieHandler
errorHandler func(http.ResponseWriter, *http.Request, string, string, string)
idTokenVerifier IDTokenVerifier
idTokenVerifier *oidc.Verifier
verifierOpts []VerifierOption
signer jose.Signer
}
@ -137,7 +137,7 @@ func (rp *relyingParty) GetRevokeEndpoint() string {
return rp.endpoints.RevokeURL
}
func (rp *relyingParty) IDTokenVerifier() IDTokenVerifier {
func (rp *relyingParty) IDTokenVerifier() *oidc.Verifier {
if rp.idTokenVerifier == nil {
rp.idTokenVerifier = NewIDTokenVerifier(rp.issuer, rp.oauthConfig.ClientID, NewRemoteKeySet(rp.httpClient, rp.endpoints.JKWsURL), rp.verifierOpts...)
}

View file

@ -9,19 +9,9 @@ import (
"github.com/zitadel/oidc/v3/pkg/oidc"
)
type IDTokenVerifier interface {
oidc.Verifier
ClientID() string
SupportedSignAlgs() []string
KeySet() oidc.KeySet
Nonce(context.Context) string
ACR() oidc.ACRVerifier
MaxAge() time.Duration
}
// 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 IDTokenVerifier) (claims C, err error) {
func VerifyTokens[C oidc.IDClaims](ctx context.Context, accessToken, idToken string, v *oidc.Verifier) (claims C, err error) {
var nilClaims C
claims, err = VerifyIDToken[C](ctx, idToken, v)
@ -36,7 +26,7 @@ func VerifyTokens[C oidc.IDClaims](ctx context.Context, accessToken, idToken str
// 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 IDTokenVerifier) (claims C, err error) {
func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v *oidc.Verifier) (claims C, err error) {
var nilClaims C
decrypted, err := oidc.DecryptToken(token)
@ -52,27 +42,27 @@ func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v IDTokenVe
return nilClaims, err
}
if err = oidc.CheckIssuer(claims, v.Issuer()); err != nil {
if err = oidc.CheckIssuer(claims, v.Issuer); err != nil {
return nilClaims, err
}
if err = oidc.CheckAudience(claims, v.ClientID()); err != nil {
if err = oidc.CheckAudience(claims, v.ClientID); err != nil {
return nilClaims, err
}
if err = oidc.CheckAuthorizedParty(claims, v.ClientID()); err != nil {
if err = oidc.CheckAuthorizedParty(claims, v.ClientID); err != nil {
return nilClaims, err
}
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil {
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs, v.KeySet); err != nil {
return nilClaims, err
}
if err = oidc.CheckExpiration(claims, v.Offset()); err != nil {
if err = oidc.CheckExpiration(claims, v.Offset); err != nil {
return nilClaims, err
}
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT(), v.Offset()); err != nil {
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT, v.Offset); err != nil {
return nilClaims, err
}
@ -80,11 +70,11 @@ func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v IDTokenVe
return nilClaims, err
}
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR()); err != nil {
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil {
return nilClaims, err
}
if err = oidc.CheckAuthTime(claims, v.MaxAge()); err != nil {
if err = oidc.CheckAuthTime(claims, v.MaxAge); err != nil {
return nilClaims, err
}
return claims, nil
@ -107,15 +97,14 @@ func VerifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAl
return nil
}
// NewIDTokenVerifier returns an implementation of `IDTokenVerifier`
// for `VerifyTokens` and `VerifyIDToken`
func NewIDTokenVerifier(issuer, clientID string, keySet oidc.KeySet, options ...VerifierOption) IDTokenVerifier {
v := &idTokenVerifier{
issuer: issuer,
clientID: clientID,
keySet: keySet,
offset: time.Second,
nonce: func(_ context.Context) string {
// NewIDTokenVerifier returns a oidc.Verifier suitable for ID token verification.
func NewIDTokenVerifier(issuer, clientID string, keySet oidc.KeySet, options ...VerifierOption) *oidc.Verifier {
v := &oidc.Verifier{
Issuer: issuer,
ClientID: clientID,
KeySet: keySet,
Offset: time.Second,
Nonce: func(_ context.Context) string {
return ""
},
}
@ -128,95 +117,47 @@ func NewIDTokenVerifier(issuer, clientID string, keySet oidc.KeySet, options ...
}
// VerifierOption is the type for providing dynamic options to the IDTokenVerifier
type VerifierOption func(*idTokenVerifier)
type VerifierOption func(*oidc.Verifier)
// 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) func(*idTokenVerifier) {
return func(v *idTokenVerifier) {
v.offset = offset
func WithIssuedAtOffset(offset time.Duration) func(*oidc.Verifier) {
return func(v *oidc.Verifier) {
v.Offset = offset
}
}
// WithIssuedAtMaxAge provides the ability to define the maximum duration between iat and now
func WithIssuedAtMaxAge(maxAge time.Duration) func(*idTokenVerifier) {
return func(v *idTokenVerifier) {
v.maxAgeIAT = maxAge
func WithIssuedAtMaxAge(maxAge time.Duration) func(*oidc.Verifier) {
return func(v *oidc.Verifier) {
v.MaxAgeIAT = maxAge
}
}
// WithNonce sets the function to check the nonce
func WithNonce(nonce func(context.Context) string) VerifierOption {
return func(v *idTokenVerifier) {
v.nonce = nonce
return func(v *oidc.Verifier) {
v.Nonce = nonce
}
}
// WithACRVerifier sets the verifier for the acr claim
func WithACRVerifier(verifier oidc.ACRVerifier) VerifierOption {
return func(v *idTokenVerifier) {
v.acr = verifier
return func(v *oidc.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 *idTokenVerifier) {
v.maxAge = maxAge
return func(v *oidc.Verifier) {
v.MaxAge = maxAge
}
}
// WithSupportedSigningAlgorithms overwrites the default RS256 signing algorithm
func WithSupportedSigningAlgorithms(algs ...string) VerifierOption {
return func(v *idTokenVerifier) {
v.supportedSignAlgs = algs
return func(v *oidc.Verifier) {
v.SupportedSignAlgs = algs
}
}
type idTokenVerifier struct {
issuer string
maxAgeIAT time.Duration
offset time.Duration
clientID string
supportedSignAlgs []string
keySet oidc.KeySet
acr oidc.ACRVerifier
maxAge time.Duration
nonce func(ctx context.Context) string
}
func (i *idTokenVerifier) Issuer() string {
return i.issuer
}
func (i *idTokenVerifier) MaxAgeIAT() time.Duration {
return i.maxAgeIAT
}
func (i *idTokenVerifier) Offset() time.Duration {
return i.offset
}
func (i *idTokenVerifier) ClientID() string {
return i.clientID
}
func (i *idTokenVerifier) SupportedSignAlgs() []string {
return i.supportedSignAlgs
}
func (i *idTokenVerifier) KeySet() oidc.KeySet {
return i.keySet
}
func (i *idTokenVerifier) Nonce(ctx context.Context) string {
return i.nonce(ctx)
}
func (i *idTokenVerifier) ACR() oidc.ACRVerifier {
return i.acr
}
func (i *idTokenVerifier) MaxAge() time.Duration {
return i.maxAge
}

View file

@ -13,16 +13,16 @@ import (
)
func TestVerifyTokens(t *testing.T) {
verifier := &idTokenVerifier{
issuer: tu.ValidIssuer,
maxAgeIAT: 2 * time.Minute,
offset: time.Second,
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
keySet: tu.KeySet{},
maxAge: 2 * time.Minute,
acr: tu.ACRVerify,
nonce: func(context.Context) string { return tu.ValidNonce },
clientID: tu.ValidClientID,
verifier := &oidc.Verifier{
Issuer: tu.ValidIssuer,
MaxAgeIAT: 2 * time.Minute,
Offset: time.Second,
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
KeySet: tu.KeySet{},
MaxAge: 2 * time.Minute,
ACR: tu.ACRVerify,
Nonce: func(context.Context) string { return tu.ValidNonce },
ClientID: tu.ValidClientID,
}
accessToken, _ := tu.ValidAccessToken()
atHash, err := oidc.ClaimHash(accessToken, tu.SignatureAlgorithm)
@ -91,15 +91,15 @@ func TestVerifyTokens(t *testing.T) {
}
func TestVerifyIDToken(t *testing.T) {
verifier := &idTokenVerifier{
issuer: tu.ValidIssuer,
maxAgeIAT: 2 * time.Minute,
offset: time.Second,
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
keySet: tu.KeySet{},
maxAge: 2 * time.Minute,
acr: tu.ACRVerify,
nonce: func(context.Context) string { return tu.ValidNonce },
verifier := &oidc.Verifier{
Issuer: tu.ValidIssuer,
MaxAgeIAT: 2 * time.Minute,
Offset: time.Second,
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
KeySet: tu.KeySet{},
MaxAge: 2 * time.Minute,
ACR: tu.ACRVerify,
Nonce: func(context.Context) string { return tu.ValidNonce },
}
tests := []struct {
@ -219,7 +219,7 @@ func TestVerifyIDToken(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
token, want := tt.tokenClaims()
verifier.clientID = tt.clientID
verifier.ClientID = tt.clientID
got, err := VerifyIDToken[*oidc.IDTokenClaims](context.Background(), token, verifier)
if tt.wantErr {
assert.Error(t, err)
@ -300,7 +300,7 @@ func TestNewIDTokenVerifier(t *testing.T) {
tests := []struct {
name string
args args
want IDTokenVerifier
want *oidc.Verifier
}{
{
name: "nil nonce", // otherwise assert.Equal will fail on the function
@ -317,16 +317,16 @@ func TestNewIDTokenVerifier(t *testing.T) {
WithSupportedSigningAlgorithms("ABC", "DEF"),
},
},
want: &idTokenVerifier{
issuer: tu.ValidIssuer,
offset: time.Minute,
maxAgeIAT: time.Hour,
clientID: tu.ValidClientID,
keySet: tu.KeySet{},
nonce: nil,
acr: nil,
maxAge: 2 * time.Hour,
supportedSignAlgs: []string{"ABC", "DEF"},
want: &oidc.Verifier{
Issuer: tu.ValidIssuer,
Offset: time.Minute,
MaxAgeIAT: time.Hour,
ClientID: tu.ValidClientID,
KeySet: tu.KeySet{},
Nonce: nil,
ACR: nil,
MaxAge: 2 * time.Hour,
SupportedSignAlgs: []string{"ABC", "DEF"},
},
},
}

View file

@ -61,10 +61,19 @@ var (
ErrAtHash = errors.New("at_hash does not correspond to access token")
)
type Verifier interface {
Issuer() string
MaxAgeIAT() time.Duration
Offset() time.Duration
// Verifier caries configuration for the various token verification
// functions. Use package specific constructor functions to know
// which values need to be set.
type Verifier struct {
Issuer string
MaxAgeIAT time.Duration
Offset time.Duration
ClientID string
SupportedSignAlgs []string
MaxAge time.Duration
ACR ACRVerifier
KeySet KeySet
Nonce func(ctx context.Context) string
}
// ACRVerifier specifies the function to be used by the `DefaultVerifier` for validating the acr claim

View file

@ -38,7 +38,7 @@ type Authorizer interface {
Storage() Storage
Decoder() httphelper.Decoder
Encoder() httphelper.Encoder
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
IDTokenHintVerifier(context.Context) *oidc.Verifier
Crypto() Crypto
RequestObjectSupported() bool
}
@ -47,7 +47,7 @@ type Authorizer interface {
// implementing its own validation mechanism for the auth request
type AuthorizeValidator interface {
Authorizer
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, IDTokenHintVerifier) (string, error)
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, *oidc.Verifier) (string, error)
}
func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
@ -204,7 +204,7 @@ func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oi
}
// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier IDTokenHintVerifier) (sub string, err error) {
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *oidc.Verifier) (sub string, err error) {
authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge)
if err != nil {
return "", err
@ -384,7 +384,7 @@ func ValidateAuthReqResponseType(client Client, responseType oidc.ResponseType)
// ValidateAuthReqIDTokenHint validates the id_token_hint (if passed as parameter in the request)
// and returns the `sub` claim
func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifier IDTokenHintVerifier) (string, error) {
func ValidateAuthReqIDTokenHint(ctx context.Context, idTokenHint string, verifier *oidc.Verifier) (string, error) {
if idTokenHint == "" {
return "", nil
}

View file

@ -146,7 +146,7 @@ func TestValidateAuthRequest(t *testing.T) {
type args struct {
authRequest *oidc.AuthRequest
storage op.Storage
verifier op.IDTokenHintVerifier
verifier *oidc.Verifier
}
tests := []struct {
name string

View file

@ -81,7 +81,7 @@ var (
)
type ClientJWTProfile interface {
JWTProfileVerifier(context.Context) JWTProfileVerifier
JWTProfileVerifier(context.Context) *JWTProfileVerifier
}
func ClientJWTAuth(ctx context.Context, ca oidc.ClientAssertionParams, verifier ClientJWTProfile) (clientID string, err error) {

View file

@ -22,7 +22,7 @@ import (
type testClientJWTProfile struct{}
func (testClientJWTProfile) JWTProfileVerifier(context.Context) op.JWTProfileVerifier { return nil }
func (testClientJWTProfile) JWTProfileVerifier(context.Context) *op.JWTProfileVerifier { return nil }
func TestClientJWTAuth(t *testing.T) {
type args struct {

View file

@ -11,6 +11,7 @@ import (
gomock "github.com/golang/mock/gomock"
http "github.com/zitadel/oidc/v3/pkg/http"
op "github.com/zitadel/oidc/v3/pkg/op"
oidc "github.com/zitadel/oidc/v3/pkg/oidc"
)
// MockAuthorizer is a mock of Authorizer interface.
@ -79,10 +80,10 @@ func (mr *MockAuthorizerMockRecorder) Encoder() *gomock.Call {
}
// IDTokenHintVerifier mocks base method.
func (m *MockAuthorizer) IDTokenHintVerifier(arg0 context.Context) op.IDTokenHintVerifier {
func (m *MockAuthorizer) IDTokenHintVerifier(arg0 context.Context) *oidc.Verifier {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IDTokenHintVerifier", arg0)
ret0, _ := ret[0].(op.IDTokenHintVerifier)
ret0, _ := ret[0].(*oidc.Verifier)
return ret0
}

View file

@ -49,7 +49,7 @@ func ExpectEncoder(a op.Authorizer) {
func ExpectVerifier(a op.Authorizer, t *testing.T) {
mockA := a.(*MockAuthorizer)
mockA.EXPECT().IDTokenHintVerifier(gomock.Any()).DoAndReturn(
func() op.IDTokenHintVerifier {
func() *oidc.Verifier {
return op.NewIDTokenHintVerifier("", nil)
})
}

View file

@ -73,8 +73,8 @@ type OpenIDProvider interface {
Storage() Storage
Decoder() httphelper.Decoder
Encoder() httphelper.Encoder
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
AccessTokenVerifier(context.Context) AccessTokenVerifier
IDTokenHintVerifier(context.Context) *oidc.Verifier
AccessTokenVerifier(context.Context) *oidc.Verifier
Crypto() Crypto
DefaultLogoutRedirectURI() string
Probes() []ProbesFn
@ -342,15 +342,15 @@ func (o *Provider) Encoder() httphelper.Encoder {
return o.encoder
}
func (o *Provider) IDTokenHintVerifier(ctx context.Context) IDTokenHintVerifier {
func (o *Provider) IDTokenHintVerifier(ctx context.Context) *oidc.Verifier {
return NewIDTokenHintVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.idTokenHintVerifierOpts...)
}
func (o *Provider) JWTProfileVerifier(ctx context.Context) JWTProfileVerifier {
func (o *Provider) JWTProfileVerifier(ctx context.Context) *JWTProfileVerifier {
return NewJWTProfileVerifier(o.Storage(), IssuerFromContext(ctx), 1*time.Hour, time.Second)
}
func (o *Provider) AccessTokenVerifier(ctx context.Context) AccessTokenVerifier {
func (o *Provider) AccessTokenVerifier(ctx context.Context) *oidc.Verifier {
return NewAccessTokenVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.accessTokenVerifierOpts...)
}

View file

@ -13,7 +13,7 @@ import (
type SessionEnder interface {
Decoder() httphelper.Decoder
Storage() Storage
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
IDTokenHintVerifier(context.Context) *oidc.Verifier
DefaultLogoutRedirectURI() string
}

View file

@ -13,7 +13,7 @@ type Introspector interface {
Decoder() httphelper.Decoder
Crypto() Crypto
Storage() Storage
AccessTokenVerifier(context.Context) AccessTokenVerifier
AccessTokenVerifier(context.Context) *oidc.Verifier
}
type IntrospectorJWTProfile interface {

View file

@ -11,7 +11,7 @@ import (
type JWTAuthorizationGrantExchanger interface {
Exchanger
JWTProfileVerifier(context.Context) JWTProfileVerifier
JWTProfileVerifier(context.Context) *JWTProfileVerifier
}
// JWTProfile handles the OAuth 2.0 JWT Profile Authorization Grant https://tools.ietf.org/html/rfc7523#section-2.1

View file

@ -20,8 +20,8 @@ type Exchanger interface {
GrantTypeJWTAuthorizationSupported() bool
GrantTypeClientCredentialsSupported() bool
GrantTypeDeviceCodeSupported() bool
AccessTokenVerifier(context.Context) AccessTokenVerifier
IDTokenHintVerifier(context.Context) IDTokenHintVerifier
AccessTokenVerifier(context.Context) *oidc.Verifier
IDTokenHintVerifier(context.Context) *oidc.Verifier
}
func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) {

View file

@ -15,14 +15,14 @@ type Revoker interface {
Decoder() httphelper.Decoder
Crypto() Crypto
Storage() Storage
AccessTokenVerifier(context.Context) AccessTokenVerifier
AccessTokenVerifier(context.Context) *oidc.Verifier
AuthMethodPrivateKeyJWTSupported() bool
AuthMethodPostSupported() bool
}
type RevokerJWTProfile interface {
Revoker
JWTProfileVerifier(context.Context) JWTProfileVerifier
JWTProfileVerifier(context.Context) *JWTProfileVerifier
}
func revocationHandler(revoker Revoker) func(http.ResponseWriter, *http.Request) {

View file

@ -14,7 +14,7 @@ type UserinfoProvider interface {
Decoder() httphelper.Decoder
Crypto() Crypto
Storage() Storage
AccessTokenVerifier(context.Context) AccessTokenVerifier
AccessTokenVerifier(context.Context) *oidc.Verifier
}
func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter, *http.Request) {

View file

@ -2,62 +2,23 @@ package op
import (
"context"
"time"
"github.com/zitadel/oidc/v3/pkg/oidc"
)
type AccessTokenVerifier interface {
oidc.Verifier
SupportedSignAlgs() []string
KeySet() oidc.KeySet
}
type accessTokenVerifier struct {
issuer string
maxAgeIAT time.Duration
offset time.Duration
supportedSignAlgs []string
keySet oidc.KeySet
}
// Issuer implements oidc.Verifier interface
func (i *accessTokenVerifier) Issuer() string {
return i.issuer
}
// MaxAgeIAT implements oidc.Verifier interface
func (i *accessTokenVerifier) MaxAgeIAT() time.Duration {
return i.maxAgeIAT
}
// Offset implements oidc.Verifier interface
func (i *accessTokenVerifier) Offset() time.Duration {
return i.offset
}
// SupportedSignAlgs implements AccessTokenVerifier interface
func (i *accessTokenVerifier) SupportedSignAlgs() []string {
return i.supportedSignAlgs
}
// KeySet implements AccessTokenVerifier interface
func (i *accessTokenVerifier) KeySet() oidc.KeySet {
return i.keySet
}
type AccessTokenVerifierOpt func(*accessTokenVerifier)
type AccessTokenVerifierOpt func(*oidc.Verifier)
func WithSupportedAccessTokenSigningAlgorithms(algs ...string) AccessTokenVerifierOpt {
return func(verifier *accessTokenVerifier) {
verifier.supportedSignAlgs = algs
return func(verifier *oidc.Verifier) {
verifier.SupportedSignAlgs = algs
}
}
func NewAccessTokenVerifier(issuer string, keySet oidc.KeySet, opts ...AccessTokenVerifierOpt) AccessTokenVerifier {
verifier := &accessTokenVerifier{
issuer: issuer,
keySet: keySet,
// NewAccessTokenVerifier returns a oidc.Verifier suitable for access token verification.
func NewAccessTokenVerifier(issuer string, keySet oidc.KeySet, opts ...AccessTokenVerifierOpt) *oidc.Verifier {
verifier := &oidc.Verifier{
Issuer: issuer,
KeySet: keySet,
}
for _, opt := range opts {
opt(verifier)
@ -66,7 +27,7 @@ func NewAccessTokenVerifier(issuer string, keySet oidc.KeySet, opts ...AccessTok
}
// VerifyAccessToken validates the access token (issuer, signature and expiration).
func VerifyAccessToken[C oidc.Claims](ctx context.Context, token string, v AccessTokenVerifier) (claims C, err error) {
func VerifyAccessToken[C oidc.Claims](ctx context.Context, token string, v *oidc.Verifier) (claims C, err error) {
var nilClaims C
decrypted, err := oidc.DecryptToken(token)
@ -78,15 +39,15 @@ func VerifyAccessToken[C oidc.Claims](ctx context.Context, token string, v Acces
return nilClaims, err
}
if err := oidc.CheckIssuer(claims, v.Issuer()); err != nil {
if err := oidc.CheckIssuer(claims, v.Issuer); err != nil {
return nilClaims, err
}
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil {
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs, v.KeySet); err != nil {
return nilClaims, err
}
if err = oidc.CheckExpiration(claims, v.Offset()); err != nil {
if err = oidc.CheckExpiration(claims, v.Offset); err != nil {
return nilClaims, err
}

View file

@ -20,7 +20,7 @@ func TestNewAccessTokenVerifier(t *testing.T) {
tests := []struct {
name string
args args
want AccessTokenVerifier
want *oidc.Verifier
}{
{
name: "simple",
@ -28,9 +28,9 @@ func TestNewAccessTokenVerifier(t *testing.T) {
issuer: tu.ValidIssuer,
keySet: tu.KeySet{},
},
want: &accessTokenVerifier{
issuer: tu.ValidIssuer,
keySet: tu.KeySet{},
want: &oidc.Verifier{
Issuer: tu.ValidIssuer,
KeySet: tu.KeySet{},
},
},
{
@ -42,10 +42,10 @@ func TestNewAccessTokenVerifier(t *testing.T) {
WithSupportedAccessTokenSigningAlgorithms("ABC", "DEF"),
},
},
want: &accessTokenVerifier{
issuer: tu.ValidIssuer,
keySet: tu.KeySet{},
supportedSignAlgs: []string{"ABC", "DEF"},
want: &oidc.Verifier{
Issuer: tu.ValidIssuer,
KeySet: tu.KeySet{},
SupportedSignAlgs: []string{"ABC", "DEF"},
},
},
}
@ -58,12 +58,12 @@ func TestNewAccessTokenVerifier(t *testing.T) {
}
func TestVerifyAccessToken(t *testing.T) {
verifier := &accessTokenVerifier{
issuer: tu.ValidIssuer,
maxAgeIAT: 2 * time.Minute,
offset: time.Second,
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
keySet: tu.KeySet{},
verifier := &oidc.Verifier{
Issuer: tu.ValidIssuer,
MaxAgeIAT: 2 * time.Minute,
Offset: time.Second,
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
KeySet: tu.KeySet{},
}
tests := []struct {

View file

@ -2,69 +2,22 @@ package op
import (
"context"
"time"
"github.com/zitadel/oidc/v3/pkg/oidc"
)
type IDTokenHintVerifier interface {
oidc.Verifier
SupportedSignAlgs() []string
KeySet() oidc.KeySet
ACR() oidc.ACRVerifier
MaxAge() time.Duration
}
type idTokenHintVerifier struct {
issuer string
maxAgeIAT time.Duration
offset time.Duration
supportedSignAlgs []string
maxAge time.Duration
acr oidc.ACRVerifier
keySet oidc.KeySet
}
func (i *idTokenHintVerifier) Issuer() string {
return i.issuer
}
func (i *idTokenHintVerifier) MaxAgeIAT() time.Duration {
return i.maxAgeIAT
}
func (i *idTokenHintVerifier) Offset() time.Duration {
return i.offset
}
func (i *idTokenHintVerifier) SupportedSignAlgs() []string {
return i.supportedSignAlgs
}
func (i *idTokenHintVerifier) KeySet() oidc.KeySet {
return i.keySet
}
func (i *idTokenHintVerifier) ACR() oidc.ACRVerifier {
return i.acr
}
func (i *idTokenHintVerifier) MaxAge() time.Duration {
return i.maxAge
}
type IDTokenHintVerifierOpt func(*idTokenHintVerifier)
type IDTokenHintVerifierOpt func(*oidc.Verifier)
func WithSupportedIDTokenHintSigningAlgorithms(algs ...string) IDTokenHintVerifierOpt {
return func(verifier *idTokenHintVerifier) {
verifier.supportedSignAlgs = algs
return func(verifier *oidc.Verifier) {
verifier.SupportedSignAlgs = algs
}
}
func NewIDTokenHintVerifier(issuer string, keySet oidc.KeySet, opts ...IDTokenHintVerifierOpt) IDTokenHintVerifier {
verifier := &idTokenHintVerifier{
issuer: issuer,
keySet: keySet,
func NewIDTokenHintVerifier(issuer string, keySet oidc.KeySet, opts ...IDTokenHintVerifierOpt) *oidc.Verifier {
verifier := &oidc.Verifier{
Issuer: issuer,
KeySet: keySet,
}
for _, opt := range opts {
opt(verifier)
@ -74,7 +27,7 @@ func NewIDTokenHintVerifier(issuer string, keySet oidc.KeySet, opts ...IDTokenHi
// VerifyIDTokenHint validates the id token according to
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
func VerifyIDTokenHint[C oidc.Claims](ctx context.Context, token string, v IDTokenHintVerifier) (claims C, err error) {
func VerifyIDTokenHint[C oidc.Claims](ctx context.Context, token string, v *oidc.Verifier) (claims C, err error) {
var nilClaims C
decrypted, err := oidc.DecryptToken(token)
@ -86,27 +39,27 @@ func VerifyIDTokenHint[C oidc.Claims](ctx context.Context, token string, v IDTok
return nilClaims, err
}
if err := oidc.CheckIssuer(claims, v.Issuer()); err != nil {
if err := oidc.CheckIssuer(claims, v.Issuer); err != nil {
return nilClaims, err
}
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs(), v.KeySet()); err != nil {
if err = oidc.CheckSignature(ctx, decrypted, payload, claims, v.SupportedSignAlgs, v.KeySet); err != nil {
return nilClaims, err
}
if err = oidc.CheckExpiration(claims, v.Offset()); err != nil {
if err = oidc.CheckExpiration(claims, v.Offset); err != nil {
return nilClaims, err
}
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT(), v.Offset()); err != nil {
if err = oidc.CheckIssuedAt(claims, v.MaxAgeIAT, v.Offset); err != nil {
return nilClaims, err
}
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR()); err != nil {
if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil {
return nilClaims, err
}
if err = oidc.CheckAuthTime(claims, v.MaxAge()); err != nil {
if err = oidc.CheckAuthTime(claims, v.MaxAge); err != nil {
return nilClaims, err
}
return claims, nil

View file

@ -20,7 +20,7 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
tests := []struct {
name string
args args
want IDTokenHintVerifier
want *oidc.Verifier
}{
{
name: "simple",
@ -28,9 +28,9 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
issuer: tu.ValidIssuer,
keySet: tu.KeySet{},
},
want: &idTokenHintVerifier{
issuer: tu.ValidIssuer,
keySet: tu.KeySet{},
want: &oidc.Verifier{
Issuer: tu.ValidIssuer,
KeySet: tu.KeySet{},
},
},
{
@ -42,10 +42,10 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
WithSupportedIDTokenHintSigningAlgorithms("ABC", "DEF"),
},
},
want: &idTokenHintVerifier{
issuer: tu.ValidIssuer,
keySet: tu.KeySet{},
supportedSignAlgs: []string{"ABC", "DEF"},
want: &oidc.Verifier{
Issuer: tu.ValidIssuer,
KeySet: tu.KeySet{},
SupportedSignAlgs: []string{"ABC", "DEF"},
},
},
}
@ -58,14 +58,14 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
}
func TestVerifyIDTokenHint(t *testing.T) {
verifier := &idTokenHintVerifier{
issuer: tu.ValidIssuer,
maxAgeIAT: 2 * time.Minute,
offset: time.Second,
supportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
maxAge: 2 * time.Minute,
acr: tu.ACRVerify,
keySet: tu.KeySet{},
verifier := &oidc.Verifier{
Issuer: tu.ValidIssuer,
MaxAgeIAT: 2 * time.Minute,
Offset: time.Second,
SupportedSignAlgs: []string{string(tu.SignatureAlgorithm)},
MaxAge: 2 * time.Minute,
ACR: tu.ACRVerify,
KeySet: tu.KeySet{},
}
tests := []struct {

View file

@ -11,28 +11,25 @@ import (
"github.com/zitadel/oidc/v3/pkg/oidc"
)
type JWTProfileVerifier interface {
// JWTProfileVerfiier extends oidc.Verifier with
// a jwtProfileKeyStorage and a function to check
// the subject in a token.
type JWTProfileVerifier struct {
oidc.Verifier
Storage() jwtProfileKeyStorage
CheckSubject(request *oidc.JWTTokenRequest) error
}
type jwtProfileVerifier struct {
storage jwtProfileKeyStorage
subjectCheck func(request *oidc.JWTTokenRequest) error
issuer string
maxAgeIAT time.Duration
offset time.Duration
Storage JWTProfileKeyStorage
CheckSubject func(request *oidc.JWTTokenRequest) error
}
// NewJWTProfileVerifier creates a oidc.Verifier for JWT Profile assertions (authorization grant and client authentication)
func NewJWTProfileVerifier(storage jwtProfileKeyStorage, issuer string, maxAgeIAT, offset time.Duration, opts ...JWTProfileVerifierOption) JWTProfileVerifier {
j := &jwtProfileVerifier{
storage: storage,
subjectCheck: SubjectIsIssuer,
issuer: issuer,
maxAgeIAT: maxAgeIAT,
offset: offset,
func NewJWTProfileVerifier(storage JWTProfileKeyStorage, issuer string, maxAgeIAT, offset time.Duration, opts ...JWTProfileVerifierOption) *JWTProfileVerifier {
j := &JWTProfileVerifier{
Verifier: oidc.Verifier{
Issuer: issuer,
MaxAgeIAT: maxAgeIAT,
Offset: offset,
},
Storage: storage,
CheckSubject: SubjectIsIssuer,
}
for _, opt := range opts {
@ -42,53 +39,35 @@ func NewJWTProfileVerifier(storage jwtProfileKeyStorage, issuer string, maxAgeIA
return j
}
type JWTProfileVerifierOption func(*jwtProfileVerifier)
type JWTProfileVerifierOption func(*JWTProfileVerifier)
// SubjectCheck sets a custom function to check the subject.
// Defaults to SubjectIsIssuer()
func SubjectCheck(check func(request *oidc.JWTTokenRequest) error) JWTProfileVerifierOption {
return func(verifier *jwtProfileVerifier) {
verifier.subjectCheck = check
return func(verifier *JWTProfileVerifier) {
verifier.CheckSubject = check
}
}
func (v *jwtProfileVerifier) Issuer() string {
return v.issuer
}
func (v *jwtProfileVerifier) Storage() jwtProfileKeyStorage {
return v.storage
}
func (v *jwtProfileVerifier) MaxAgeIAT() time.Duration {
return v.maxAgeIAT
}
func (v *jwtProfileVerifier) Offset() time.Duration {
return v.offset
}
func (v *jwtProfileVerifier) CheckSubject(request *oidc.JWTTokenRequest) error {
return v.subjectCheck(request)
}
// VerifyJWTAssertion verifies the assertion string from JWT Profile (authorization grant and client authentication)
//
// checks audience, exp, iat, signature and that issuer and sub are the same
func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerifier) (*oidc.JWTTokenRequest, error) {
func VerifyJWTAssertion(ctx context.Context, assertion string, v *JWTProfileVerifier) (*oidc.JWTTokenRequest, error) {
request := new(oidc.JWTTokenRequest)
payload, err := oidc.ParseToken(assertion, request)
if err != nil {
return nil, err
}
if err = oidc.CheckAudience(request, v.Issuer()); err != nil {
if err = oidc.CheckAudience(request, v.Issuer); err != nil {
return nil, err
}
if err = oidc.CheckExpiration(request, v.Offset()); err != nil {
if err = oidc.CheckExpiration(request, v.Offset); err != nil {
return nil, err
}
if err = oidc.CheckIssuedAt(request, v.MaxAgeIAT(), v.Offset()); err != nil {
if err = oidc.CheckIssuedAt(request, v.MaxAgeIAT, v.Offset); err != nil {
return nil, err
}
@ -96,17 +75,18 @@ func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerif
return nil, err
}
keySet := &jwtProfileKeySet{storage: v.Storage(), clientID: request.Issuer}
keySet := &jwtProfileKeySet{storage: v.Storage, clientID: request.Issuer}
if err = oidc.CheckSignature(ctx, assertion, payload, request, nil, keySet); err != nil {
return nil, err
}
return request, nil
}
type jwtProfileKeyStorage interface {
type JWTProfileKeyStorage interface {
GetKeyByIDAndClientID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error)
}
// SubjectIsIssuer
func SubjectIsIssuer(request *oidc.JWTTokenRequest) error {
if request.Issuer != request.Subject {
return errors.New("delegation not allowed, issuer and sub must be identical")
@ -115,7 +95,7 @@ func SubjectIsIssuer(request *oidc.JWTTokenRequest) error {
}
type jwtProfileKeySet struct {
storage jwtProfileKeyStorage
storage JWTProfileKeyStorage
clientID string
}