fix(oidc): use authenticated encryption for opaque tokens (#12017)

# Which Problems Are Solved

Opaque tokens now use authenticated encryption.

# How the Problems Are Solved

- Upgrade zitadel/oidc to v3.47
- Copy crypto implementation for refresh and session tokens (internal to
zitadel)
- Added config that allows validating old tokens for gradual roll-out

# Additional Changes

- Set NX cache for `integration-test-build` to `false`, working on a
seperate fix.

# Additional Context

- closes #11315

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: muhlemmer <[email protected]>
This commit is contained in:
Tim Möhlmann
2026-04-13 10:59:50 +00:00
committed by GitHub
co-authored by copilot-swe-agent[bot] <198982749+[email protected]> muhlemmer
parent a28ee63b02
commit 01fe34a526
46 changed files with 406 additions and 201 deletions
+2 -2
View File
@@ -9,6 +9,6 @@ import (
"github.com/zitadel/zitadel/internal/query"
)
func Start(queries *query.Queries, es *eventstore.Eventstore, dbClient *database.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, externalSecure bool) (repository.Repository, error) {
return eventsourcing.Start(queries, es, dbClient, keyEncryptionAlgorithm, externalSecure)
func Start(queries *query.Queries, es *eventstore.Eventstore, dbClient *database.DB, authAlgorithm crypto.AuthAlgorithm, externalSecure bool) (repository.Repository, error) {
return eventsourcing.Start(queries, es, dbClient, authAlgorithm, externalSecure)
}
@@ -2,7 +2,6 @@ package eventstore
import (
"context"
"encoding/base64"
"fmt"
"slices"
"strings"
@@ -30,11 +29,11 @@ import (
)
type TokenVerifierRepo struct {
TokenVerificationKey crypto.EncryptionAlgorithm
Eventstore *eventstore.Eventstore
View *view.View
Query *query.Queries
ExternalSecure bool
AuthAlgorithm crypto.AuthAlgorithm
Eventstore *eventstore.Eventstore
View *view.View
Query *query.Queries
ExternalSecure bool
}
func (repo *TokenVerifierRepo) Health() error {
@@ -306,7 +305,7 @@ func (repo *TokenVerifierRepo) getUserEvents(ctx context.Context, userID, instan
func (repo *TokenVerifierRepo) getTokenIDAndSubject(ctx context.Context, accessToken string) (tokenID string, subject string, valid bool) {
// accessToken can be either opaque or JWT
// let's try opaque first:
tokenIDSubject, err := repo.decryptAccessToken(accessToken)
tokenIDSubject, err := repo.AuthAlgorithm.DecryptToken(accessToken)
if err != nil {
logging.WithError(err).Warn("token verifier repo: decrypt access token")
// if decryption did not work, it might be a JWT
@@ -329,18 +328,6 @@ func (repo *TokenVerifierRepo) jwtTokenVerifier(ctx context.Context) *op.AccessT
return op.NewAccessTokenVerifier(http_util.DomainContext(ctx).Origin(), keySet)
}
func (repo *TokenVerifierRepo) decryptAccessToken(token string) (string, error) {
tokenData, err := base64.RawURLEncoding.DecodeString(token)
if err != nil {
return "", zerrors.ThrowUnauthenticated(nil, "APP-ASdgg", "invalid token")
}
tokenIDSubject, err := repo.TokenVerificationKey.DecryptString(tokenData, repo.TokenVerificationKey.EncryptionKeyID())
if err != nil {
return "", zerrors.ThrowUnauthenticated(nil, "APP-8EF0zZ", "invalid token")
}
return tokenIDSubject, nil
}
func verifyAudience(audience []string, verifierClientID, projectID string) error {
for _, aud := range audience {
if verifierClientID == aud || projectID == aud {
@@ -17,7 +17,7 @@ type EsRepository struct {
authz_es.TokenVerifierRepo
}
func Start(queries *query.Queries, es *eventstore.Eventstore, dbClient *database.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, externalSecure bool) (repository.Repository, error) {
func Start(queries *query.Queries, es *eventstore.Eventstore, dbClient *database.DB, authAlgorithm crypto.AuthAlgorithm, externalSecure bool) (repository.Repository, error) {
view, err := authz_view.StartView(dbClient, queries)
if err != nil {
return nil, err
@@ -28,11 +28,11 @@ func Start(queries *query.Queries, es *eventstore.Eventstore, dbClient *database
Queries: queries,
},
authz_es.TokenVerifierRepo{
TokenVerificationKey: keyEncryptionAlgorithm,
Eventstore: es,
View: view,
Query: queries,
ExternalSecure: externalSecure,
AuthAlgorithm: authAlgorithm,
Eventstore: es,
View: view,
Query: queries,
ExternalSecure: externalSecure,
},
}, nil
}