feat(op): split the access and ID token hint verifiers (#525)

* feat(op): split the access and ID token hint verifiers

In zitadel we require different behaviors wrt public key expiry between access tokens and ID token hints.
This change splits the two verifiers in the OP.
The default is still based on Storage and passed to both verifier fields.

* add new options to tests
This commit is contained in:
Tim Möhlmann 2024-01-26 17:44:50 +02:00 committed by GitHub
parent 437a0497ab
commit e9bd7d7bac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 28 deletions

View file

@ -257,13 +257,16 @@ func NewForwardedOpenIDProvider(path string, config *Config, storage Storage, op
// op.AuthCallbackURL(provider) which is probably /callback. On the redirect back // op.AuthCallbackURL(provider) which is probably /callback. On the redirect back
// to the AuthCallbackURL, the request id should be passed as the "id" parameter. // to the AuthCallbackURL, the request id should be passed as the "id" parameter.
func NewProvider(config *Config, storage Storage, issuer func(insecure bool) (IssuerFromRequest, error), opOpts ...Option) (_ *Provider, err error) { func NewProvider(config *Config, storage Storage, issuer func(insecure bool) (IssuerFromRequest, error), opOpts ...Option) (_ *Provider, err error) {
keySet := &OpenIDKeySet{storage}
o := &Provider{ o := &Provider{
config: config, config: config,
storage: storage, storage: storage,
endpoints: DefaultEndpoints, accessTokenKeySet: keySet,
timer: make(<-chan time.Time), idTokenHinKeySet: keySet,
corsOpts: &defaultCORSOptions, endpoints: DefaultEndpoints,
logger: slog.Default(), timer: make(<-chan time.Time),
corsOpts: &defaultCORSOptions,
logger: slog.Default(),
} }
for _, optFunc := range opOpts { for _, optFunc := range opOpts {
@ -276,19 +279,11 @@ func NewProvider(config *Config, storage Storage, issuer func(insecure bool) (Is
if err != nil { if err != nil {
return nil, err return nil, err
} }
o.Handler = CreateRouter(o, o.interceptors...) o.Handler = CreateRouter(o, o.interceptors...)
o.decoder = schema.NewDecoder() o.decoder = schema.NewDecoder()
o.decoder.IgnoreUnknownKeys(true) o.decoder.IgnoreUnknownKeys(true)
o.encoder = oidc.NewEncoder() o.encoder = oidc.NewEncoder()
o.crypto = NewAESCrypto(config.CryptoKey) o.crypto = NewAESCrypto(config.CryptoKey)
// Avoid potential race conditions by calling these early
_ = o.openIDKeySet() // sets keySet
return o, nil return o, nil
} }
@ -299,7 +294,8 @@ type Provider struct {
insecure bool insecure bool
endpoints *Endpoints endpoints *Endpoints
storage Storage storage Storage
keySet *openIDKeySet accessTokenKeySet oidc.KeySet
idTokenHinKeySet oidc.KeySet
crypto Crypto crypto Crypto
decoder *schema.Decoder decoder *schema.Decoder
encoder *schema.Encoder encoder *schema.Encoder
@ -435,7 +431,7 @@ func (o *Provider) Encoder() httphelper.Encoder {
} }
func (o *Provider) IDTokenHintVerifier(ctx context.Context) *IDTokenHintVerifier { func (o *Provider) IDTokenHintVerifier(ctx context.Context) *IDTokenHintVerifier {
return NewIDTokenHintVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.idTokenHintVerifierOpts...) return NewIDTokenHintVerifier(IssuerFromContext(ctx), o.idTokenHinKeySet, o.idTokenHintVerifierOpts...)
} }
func (o *Provider) JWTProfileVerifier(ctx context.Context) *JWTProfileVerifier { func (o *Provider) JWTProfileVerifier(ctx context.Context) *JWTProfileVerifier {
@ -443,14 +439,7 @@ func (o *Provider) JWTProfileVerifier(ctx context.Context) *JWTProfileVerifier {
} }
func (o *Provider) AccessTokenVerifier(ctx context.Context) *AccessTokenVerifier { func (o *Provider) AccessTokenVerifier(ctx context.Context) *AccessTokenVerifier {
return NewAccessTokenVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.accessTokenVerifierOpts...) return NewAccessTokenVerifier(IssuerFromContext(ctx), o.accessTokenKeySet, o.accessTokenVerifierOpts...)
}
func (o *Provider) openIDKeySet() oidc.KeySet {
if o.keySet == nil {
o.keySet = &openIDKeySet{o.Storage()}
}
return o.keySet
} }
func (o *Provider) Crypto() Crypto { func (o *Provider) Crypto() Crypto {
@ -480,13 +469,13 @@ func (o *Provider) HttpHandler() http.Handler {
return o return o
} }
type openIDKeySet struct { type OpenIDKeySet struct {
Storage Storage
} }
// VerifySignature implements the oidc.KeySet interface // VerifySignature implements the oidc.KeySet interface
// providing an implementation for the keys stored in the OP Storage interface // providing an implementation for the keys stored in the OP Storage interface
func (o *openIDKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) { func (o *OpenIDKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
keySet, err := o.Storage.KeySet(ctx) keySet, err := o.Storage.KeySet(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching keys: %w", err) return nil, fmt.Errorf("error fetching keys: %w", err)
@ -617,6 +606,15 @@ func WithHttpInterceptors(interceptors ...HttpInterceptor) Option {
} }
} }
// WithAccessTokenKeySet allows passing a KeySet with public keys for Access Token verification.
// The default KeySet uses the [Storage] interface
func WithAccessTokenKeySet(keySet oidc.KeySet) Option {
return func(o *Provider) error {
o.accessTokenKeySet = keySet
return nil
}
}
func WithAccessTokenVerifierOpts(opts ...AccessTokenVerifierOpt) Option { func WithAccessTokenVerifierOpts(opts ...AccessTokenVerifierOpt) Option {
return func(o *Provider) error { return func(o *Provider) error {
o.accessTokenVerifierOpts = opts o.accessTokenVerifierOpts = opts
@ -624,6 +622,15 @@ func WithAccessTokenVerifierOpts(opts ...AccessTokenVerifierOpt) Option {
} }
} }
// WithIDTokenHintKeySet allows passing a KeySet with public keys for ID Token Hint verification.
// The default KeySet uses the [Storage] interface.
func WithIDTokenHintKeySet(keySet oidc.KeySet) Option {
return func(o *Provider) error {
o.idTokenHinKeySet = keySet
return nil
}
}
func WithIDTokenHintVerifierOpts(opts ...IDTokenHintVerifierOpt) Option { func WithIDTokenHintVerifierOpts(opts ...IDTokenHintVerifierOpt) Option {
return func(o *Provider) error { return func(o *Provider) error {
o.idTokenHintVerifierOpts = opts o.idTokenHintVerifierOpts = opts

View file

@ -58,8 +58,12 @@ func init() {
} }
func newTestProvider(config *op.Config) op.OpenIDProvider { func newTestProvider(config *op.Config) op.OpenIDProvider {
provider, err := op.NewOpenIDProvider(testIssuer, config, storage := storage.NewStorage(storage.NewUserStore(testIssuer))
storage.NewStorage(storage.NewUserStore(testIssuer)), op.WithAllowInsecure(), keySet := &op.OpenIDKeySet{storage}
provider, err := op.NewOpenIDProvider(testIssuer, config, storage,
op.WithAllowInsecure(),
op.WithAccessTokenKeySet(keySet),
op.WithIDTokenHintKeySet(keySet),
) )
if err != nil { if err != nil {
panic(err) panic(err)