From 1683b319aeaa7b15d43b306f1774154cd94000a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 1 Sep 2023 11:53:14 +0300 Subject: [PATCH] feat(op): add opentelemetry to token endpoint (#436) * feat(op): add opentelemetry to token endpoint * drop go 1.18, add 1.21, do not fail fast --- .github/workflows/release.yml | 3 ++- README.md | 6 +++--- go.mod | 7 ++++++- go.sum | 11 +++++++++++ pkg/op/auth_request.go | 4 ++++ pkg/op/device.go | 4 ++++ pkg/op/op.go | 8 ++++++++ pkg/op/token.go | 14 ++++++++++++++ pkg/op/token_client_credentials.go | 13 +++++++++++++ pkg/op/token_code.go | 13 +++++++++++++ pkg/op/token_exchange.go | 4 ++++ pkg/op/token_jwt_profile.go | 7 +++++++ pkg/op/token_refresh.go | 10 ++++++++++ pkg/op/token_request.go | 12 +++++++++++- pkg/op/verifier_jwt_profile.go | 3 +++ 15 files changed, 113 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b21e7e9..2b3dac4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,8 +15,9 @@ jobs: test: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: - go: ['1.18', '1.19', '1.20'] + go: ['1.19', '1.20', '1.21'] name: Go ${{ matrix.go }} test steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index e5a992f..0cf5274 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,10 @@ Versions that also build are marked with :warning:. | Version | Supported | | ------- | ------------------ | -| <1.18 | :x: | -| 1.18 | :warning: | -| 1.19 | :white_check_mark: | +| <1.19 | :x: | +| 1.19 | :warning: | | 1.20 | :white_check_mark: | +| 1.21 | :white_check_mark: | ## Why another library diff --git a/go.mod b/go.mod index 6abc07b..a1b1714 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/zitadel/oidc/v2 -go 1.18 +go 1.19 require ( github.com/golang/mock v1.6.0 @@ -14,6 +14,8 @@ require ( github.com/rs/cors v1.9.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/otel v1.17.0 + go.opentelemetry.io/otel/trace v1.17.0 golang.org/x/oauth2 v0.11.0 golang.org/x/text v0.12.0 gopkg.in/square/go-jose.v2 v2.6.0 @@ -21,9 +23,12 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/otel/metric v1.17.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sys v0.11.0 // indirect diff --git a/go.sum b/go.sum index be1233b..83d02bb 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,11 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -44,6 +49,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= +go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= +go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc= +go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= +go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= +go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= diff --git a/pkg/op/auth_request.go b/pkg/op/auth_request.go index 5621951..5845756 100644 --- a/pkg/op/auth_request.go +++ b/pkg/op/auth_request.go @@ -464,6 +464,10 @@ func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthReques // AuthResponseToken creates the successful token(s) authentication response func AuthResponseToken(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer, client Client) { + ctx, span := tracer.Start(r.Context(), "AuthResponseToken") + defer span.End() + r = r.WithContext(ctx) + createAccessToken := authReq.GetResponseType() != oidc.ResponseTypeIDTokenOnly resp, err := CreateTokenResponse(r.Context(), authReq, client, authorizer, createAccessToken, "", "") if err != nil { diff --git a/pkg/op/device.go b/pkg/op/device.go index f584c31..2d3a8c9 100644 --- a/pkg/op/device.go +++ b/pkg/op/device.go @@ -196,6 +196,10 @@ func (r *deviceAccessTokenRequest) GetScopes() []string { } func DeviceAccessToken(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + ctx, span := tracer.Start(r.Context(), "DeviceAccessToken") + defer span.End() + r = r.WithContext(ctx) + if err := deviceAccessToken(w, r, exchanger); err != nil { RequestError(w, r, err) } diff --git a/pkg/op/op.go b/pkg/op/op.go index fc5262a..484149e 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -9,6 +9,8 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/schema" "github.com/rs/cors" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" "golang.org/x/text/language" "gopkg.in/square/go-jose.v2" @@ -67,6 +69,12 @@ var ( } ) +var tracer trace.Tracer + +func init() { + tracer = otel.Tracer("github.com/zitadel/oidc/pkg/op") +} + type OpenIDProvider interface { Configuration Storage() Storage diff --git a/pkg/op/token.go b/pkg/op/token.go index 6dfc993..ae82b06 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -28,6 +28,9 @@ type AccessTokenClient interface { } func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Client, creator TokenCreator, createAccessToken bool, code, refreshToken string) (*oidc.AccessTokenResponse, error) { + ctx, span := tracer.Start(ctx, "CreateTokenResponse") + defer span.End() + var accessToken, newRefreshToken string var validity time.Duration if createAccessToken { @@ -84,6 +87,9 @@ func needsRefreshToken(tokenRequest TokenRequest, client AccessTokenClient) bool } func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client AccessTokenClient, refreshToken string) (accessToken, newRefreshToken string, validity time.Duration, err error) { + ctx, span := tracer.Start(ctx, "CreateAccessToken") + defer span.End() + id, newRefreshToken, exp, err := createTokens(ctx, tokenRequest, creator.Storage(), refreshToken, client) if err != nil { return "", "", 0, err @@ -97,7 +103,9 @@ func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTok accessToken, err = CreateJWT(ctx, IssuerFromContext(ctx), tokenRequest, exp, id, client, creator.Storage()) return } + _, span = tracer.Start(ctx, "CreateBearerToken") accessToken, err = CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto()) + span.End() return } @@ -106,6 +114,9 @@ func CreateBearerToken(tokenID, subject string, crypto Crypto) (string, error) { } func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, client AccessTokenClient, storage Storage) (string, error) { + ctx, span := tracer.Start(ctx, "CreateJWT") + defer span.End() + claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID(), client.ClockSkew()) if client != nil { restrictedScopes := client.RestrictAdditionalAccessTokenScopes()(tokenRequest.GetScopes()) @@ -152,6 +163,9 @@ type IDTokenRequest interface { } func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, validity time.Duration, accessToken, code string, storage Storage, client Client) (string, error) { + ctx, span := tracer.Start(ctx, "CreateIDToken") + defer span.End() + exp := time.Now().UTC().Add(client.ClockSkew()).Add(validity) var acr, nonce string if authRequest, ok := request.(AuthRequest); ok { diff --git a/pkg/op/token_client_credentials.go b/pkg/op/token_client_credentials.go index fc31d57..0b91a36 100644 --- a/pkg/op/token_client_credentials.go +++ b/pkg/op/token_client_credentials.go @@ -12,6 +12,10 @@ import ( // ClientCredentialsExchange handles the OAuth 2.0 client_credentials grant, including // parsing, validating, authorizing the client and finally returning a token func ClientCredentialsExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + ctx, span := tracer.Start(r.Context(), "ClientCredentialsExchange") + defer span.End() + r = r.WithContext(ctx) + request, err := ParseClientCredentialsRequest(r, exchanger.Decoder()) if err != nil { RequestError(w, r, err) @@ -66,6 +70,9 @@ func ParseClientCredentialsRequest(r *http.Request, decoder httphelper.Decoder) // ValidateClientCredentialsRequest validates the client_credentials request parameters including authorization check of the client // and returns a TokenRequest and Client implementation to be used in the client_credentials response, resp. creation of the corresponding access_token. func ValidateClientCredentialsRequest(ctx context.Context, request *oidc.ClientCredentialsRequest, exchanger Exchanger) (TokenRequest, Client, error) { + ctx, span := tracer.Start(ctx, "ValidateClientCredentialsRequest") + defer span.End() + storage, ok := exchanger.Storage().(ClientCredentialsStorage) if !ok { return nil, nil, oidc.ErrUnsupportedGrantType().WithDescription("client_credentials grant not supported") @@ -85,6 +92,9 @@ func ValidateClientCredentialsRequest(ctx context.Context, request *oidc.ClientC } func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientCredentialsRequest, storage ClientCredentialsStorage) (Client, error) { + ctx, span := tracer.Start(ctx, "AuthorizeClientCredentialsClient") + defer span.End() + client, err := storage.ClientCredentials(ctx, request.ClientID, request.ClientSecret) if err != nil { return nil, oidc.ErrInvalidClient().WithParent(err) @@ -98,6 +108,9 @@ func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientC } func CreateClientCredentialsTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator, client Client) (*oidc.AccessTokenResponse, error) { + ctx, span := tracer.Start(ctx, "CreateClientCredentialsTokenResponse") + defer span.End() + accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, client.AccessTokenType(), creator, client, "") if err != nil { return nil, err diff --git a/pkg/op/token_code.go b/pkg/op/token_code.go index 565a477..f2162ef 100644 --- a/pkg/op/token_code.go +++ b/pkg/op/token_code.go @@ -11,6 +11,10 @@ import ( // CodeExchange handles the OAuth 2.0 authorization_code grant, including // parsing, validating, authorizing the client and finally exchanging the code for tokens func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + ctx, span := tracer.Start(r.Context(), "CodeExchange") + defer span.End() + r = r.WithContext(ctx) + tokenReq, err := ParseAccessTokenRequest(r, exchanger.Decoder()) if err != nil { RequestError(w, r, err) @@ -45,6 +49,9 @@ func ParseAccessTokenRequest(r *http.Request, decoder httphelper.Decoder) (*oidc // ValidateAccessTokenRequest validates the token request parameters including authorization check of the client // and returns the previous created auth request corresponding to the auth code func ValidateAccessTokenRequest(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (AuthRequest, Client, error) { + ctx, span := tracer.Start(ctx, "ValidateAccessTokenRequest") + defer span.End() + authReq, client, err := AuthorizeCodeClient(ctx, tokenReq, exchanger) if err != nil { return nil, nil, err @@ -64,6 +71,9 @@ func ValidateAccessTokenRequest(ctx context.Context, tokenReq *oidc.AccessTokenR // AuthorizeCodeClient checks the authorization of the client and that the used method was the one previously registered. // It than returns the auth request corresponding to the auth code func AuthorizeCodeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (request AuthRequest, client Client, err error) { + ctx, span := tracer.Start(ctx, "AuthorizeCodeClient") + defer span.End() + if tokenReq.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion { jwtExchanger, ok := exchanger.(JWTAuthorizationGrantExchanger) if !ok || !exchanger.AuthMethodPrivateKeyJWTSupported() { @@ -104,6 +114,9 @@ func AuthorizeCodeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest, // AuthRequestByCode returns the AuthRequest previously created from Storage corresponding to the auth code or an error func AuthRequestByCode(ctx context.Context, storage Storage, code string) (AuthRequest, error) { + ctx, span := tracer.Start(ctx, "AuthRequestByCode") + defer span.End() + authReq, err := storage.AuthRequestByCode(ctx, code) if err != nil { return nil, oidc.ErrInvalidGrant().WithDescription("invalid code").WithParent(err) diff --git a/pkg/op/token_exchange.go b/pkg/op/token_exchange.go index 5a2387d..4f1ed43 100644 --- a/pkg/op/token_exchange.go +++ b/pkg/op/token_exchange.go @@ -134,6 +134,10 @@ func (r *tokenExchangeRequest) SetSubject(subject string) { // TokenExchange handles the OAuth 2.0 token exchange grant ("urn:ietf:params:oauth:grant-type:token-exchange") func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + ctx, span := tracer.Start(r.Context(), "TokenExchange") + defer span.End() + r = r.WithContext(ctx) + tokenExchangeReq, clientID, clientSecret, err := ParseTokenExchangeRequest(r, exchanger.Decoder()) if err != nil { RequestError(w, r, err) diff --git a/pkg/op/token_jwt_profile.go b/pkg/op/token_jwt_profile.go index 23bac9a..5a94d99 100644 --- a/pkg/op/token_jwt_profile.go +++ b/pkg/op/token_jwt_profile.go @@ -16,6 +16,10 @@ type JWTAuthorizationGrantExchanger interface { // JWTProfile handles the OAuth 2.0 JWT Profile Authorization Grant https://tools.ietf.org/html/rfc7523#section-2.1 func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger JWTAuthorizationGrantExchanger) { + ctx, span := tracer.Start(r.Context(), "JWTProfile") + defer span.End() + r = r.WithContext(ctx) + profileRequest, err := ParseJWTProfileGrantRequest(r, exchanger.Decoder()) if err != nil { RequestError(w, r, err) @@ -56,6 +60,9 @@ func ParseJWTProfileGrantRequest(r *http.Request, decoder httphelper.Decoder) (* // CreateJWTTokenResponse creates an access_token response for a JWT Profile Grant request // by default the access_token is an opaque string, but can be specified by implementing the JWTProfileTokenStorage interface func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator) (*oidc.AccessTokenResponse, error) { + ctx, span := tracer.Start(ctx, "CreateJWTTokenResponse") + defer span.End() + // return an opaque token as default to not break current implementations tokenType := AccessTokenTypeBearer diff --git a/pkg/op/token_refresh.go b/pkg/op/token_refresh.go index 148d2a4..efb6fc0 100644 --- a/pkg/op/token_refresh.go +++ b/pkg/op/token_refresh.go @@ -24,6 +24,10 @@ type RefreshTokenRequest interface { // RefreshTokenExchange handles the OAuth 2.0 refresh_token grant, including // parsing, validating, authorizing the client and finally exchanging the refresh_token for new tokens func RefreshTokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + ctx, span := tracer.Start(r.Context(), "RefreshTokenExchange") + defer span.End() + r = r.WithContext(ctx) + tokenReq, err := ParseRefreshTokenRequest(r, exchanger.Decoder()) if err != nil { RequestError(w, r, err) @@ -54,6 +58,9 @@ func ParseRefreshTokenRequest(r *http.Request, decoder httphelper.Decoder) (*oid // ValidateRefreshTokenRequest validates the refresh_token request parameters including authorization check of the client // and returns the data representing the original auth request corresponding to the refresh_token func ValidateRefreshTokenRequest(ctx context.Context, tokenReq *oidc.RefreshTokenRequest, exchanger Exchanger) (RefreshTokenRequest, Client, error) { + ctx, span := tracer.Start(ctx, "ValidateRefreshTokenRequest") + defer span.End() + if tokenReq.RefreshToken == "" { return nil, nil, oidc.ErrInvalidRequest().WithDescription("refresh_token missing") } @@ -89,6 +96,9 @@ func ValidateRefreshTokenScopes(requestedScopes []string, authRequest RefreshTok // AuthorizeRefreshClient checks the authorization of the client and that the used method was the one previously registered. // It than returns the data representing the original auth request corresponding to the refresh_token func AuthorizeRefreshClient(ctx context.Context, tokenReq *oidc.RefreshTokenRequest, exchanger Exchanger) (request RefreshTokenRequest, client Client, err error) { + ctx, span := tracer.Start(ctx, "AuthorizeRefreshClient") + defer span.End() + if tokenReq.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion { jwtExchanger, ok := exchanger.(JWTAuthorizationGrantExchanger) if !ok || !exchanger.AuthMethodPrivateKeyJWTSupported() { diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go index b9e9805..1207375 100644 --- a/pkg/op/token_request.go +++ b/pkg/op/token_request.go @@ -26,7 +26,10 @@ type Exchanger interface { func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - Exchange(w, r, exchanger) + ctx, span := tracer.Start(r.Context(), "tokenHandler") + defer span.End() + + Exchange(w, r.WithContext(ctx), exchanger) } } @@ -79,6 +82,10 @@ type AuthenticatedTokenRequest interface { // ParseAuthenticatedTokenRequest parses the client_id and client_secret from the HTTP request from either // HTTP Basic Auth header or form body and sets them into the provided authenticatedTokenRequest interface func ParseAuthenticatedTokenRequest(r *http.Request, decoder httphelper.Decoder, request AuthenticatedTokenRequest) error { + ctx, span := tracer.Start(r.Context(), "ParseAuthenticatedTokenRequest") + defer span.End() + r = r.WithContext(ctx) + err := r.ParseForm() if err != nil { return oidc.ErrInvalidRequest().WithDescription("error parsing form").WithParent(err) @@ -128,6 +135,9 @@ func AuthorizeCodeChallenge(tokenReq *oidc.AccessTokenRequest, challenge *oidc.C // AuthorizePrivateJWTKey authorizes a client by validating the client_assertion's signature with a previously // registered public key (JWT Profile) func AuthorizePrivateJWTKey(ctx context.Context, clientAssertion string, exchanger JWTAuthorizationGrantExchanger) (Client, error) { + ctx, span := tracer.Start(ctx, "AuthorizePrivateJWTKey") + defer span.End() + jwtReq, err := VerifyJWTAssertion(ctx, clientAssertion, exchanger.JWTProfileVerifier(ctx)) if err != nil { return nil, err diff --git a/pkg/op/verifier_jwt_profile.go b/pkg/op/verifier_jwt_profile.go index 4d83c59..e7c9611 100644 --- a/pkg/op/verifier_jwt_profile.go +++ b/pkg/op/verifier_jwt_profile.go @@ -74,6 +74,9 @@ func (v *jwtProfileVerifier) CheckSubject(request *oidc.JWTTokenRequest) error { // // 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) { + ctx, span := tracer.Start(ctx, "VerifyJWTAssertion") + defer span.End() + request := new(oidc.JWTTokenRequest) payload, err := oidc.ParseToken(assertion, request) if err != nil {