diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2023-08-27 12:52:57 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-27 12:52:57 +1000 |
| commit | 34b7a47bc85f38b9d27184a7ecba238074406890 (patch) | |
| tree | 43a33c0c9f2cb86fbb2159c81ade91bec357910e /internal/handlers/handler_oauth_introspection.go | |
| parent | 8d196b70d5b432c5d1ffb8a10d6088e065ca1894 (diff) | |
feat(oidc): jwt response for introspection (#5840)
This implements the standard for JWT encoded and signed responses from the introspection endpoint. This has been implemented as per the IETF draft https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-introspection-response and as it is a draft (it is also an expired draft) so it should be noted that this implementation may be removed or changed without any regard for breaking changes. While this factor points in the direction of this never receiving ratification the IANA has accepted registration of the metadata parameters for this specification which points to the fact that it probably will.
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Diffstat (limited to 'internal/handlers/handler_oauth_introspection.go')
| -rw-r--r-- | internal/handlers/handler_oauth_introspection.go | 97 |
1 files changed, 94 insertions, 3 deletions
diff --git a/internal/handlers/handler_oauth_introspection.go b/internal/handlers/handler_oauth_introspection.go index 6ef375986..cbb2a4cc3 100644 --- a/internal/handlers/handler_oauth_introspection.go +++ b/internal/handlers/handler_oauth_introspection.go @@ -1,10 +1,16 @@ package handlers import ( + "encoding/json" "net/http" + "net/url" + "time" "github.com/google/uuid" "github.com/ory/fosite" + "github.com/ory/fosite/token/jwt" + "github.com/pkg/errors" + "github.com/valyala/fasthttp" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/oidc" @@ -38,11 +44,96 @@ func OAuthIntrospectionPOST(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter return } - requester := responder.GetAccessRequester() + ctx.Logger.Tracef("Introspection Request with id '%s' yeilded a %s (active: %t) requested at %s created with request id '%s' on client with id '%s'", requestID, responder.GetTokenUse(), responder.IsActive(), responder.GetAccessRequester().GetRequestedAt().String(), responder.GetAccessRequester().GetID(), responder.GetAccessRequester().GetClient().GetID()) - ctx.Logger.Tracef("Introspection Request with id '%s' yeilded a %s (active: %t) requested at %s created with request id '%s' on client with id '%s'", requestID, responder.GetTokenUse(), responder.IsActive(), requester.GetRequestedAt().String(), requester.GetID(), requester.GetClient().GetID()) + aud, introspection := oidc.IntrospectionResponseToMap(responder) - ctx.Providers.OpenIDConnect.WriteIntrospectionResponse(ctx, rw, responder) + var ( + client oidc.Client + ok bool + ) + + if client, ok = responder.GetAccessRequester().GetClient().(oidc.Client); !ok { + ctx.Logger.Errorf("Introspection Request with id '%s' failed with error: %s", requestID, oidc.ErrorToDebugRFC6749Error(fosite.ErrInvalidClient.WithDebugf("The client does not implement the correct type as it's a '%T'", responder.GetAccessRequester().GetClient()))) + + ctx.Providers.OpenIDConnect.WriteIntrospectionError(ctx, rw, fosite.ErrInvalidClient) + + return + } + + switch alg := client.GetIntrospectionSignedResponseAlg(); alg { + case oidc.SigningAlgNone: + rw.Header().Set(fasthttp.HeaderContentType, "application/json; charset=utf-8") + rw.Header().Set(fasthttp.HeaderCacheControl, "no-store") + rw.Header().Set(fasthttp.HeaderPragma, "no-cache") + rw.WriteHeader(http.StatusOK) + + _ = json.NewEncoder(rw).Encode(introspection) + default: + var ( + issuer *url.URL + token string + jwk *oidc.JWK + jti uuid.UUID + ) + + if issuer, err = ctx.IssuerURL(); err != nil { + ctx.Logger.WithError(err).Errorf("Error occurred determining issuer") + + ctx.Providers.OpenIDConnect.WriteIntrospectionError(ctx, rw, errors.WithStack(fosite.ErrServerError.WithHint("Failed to lookup required information to perform this request.").WithDebugf("The issuer could not be determined with error %+v.", err))) + + return + } + + if jwk = ctx.Providers.OpenIDConnect.KeyManager.Get(ctx, client.GetIntrospectionSignedResponseKeyID(), alg); jwk == nil { + ctx.Logger.WithError(err).Errorf("Introspection Request with id '%s' failed to lookup key for key manager due to likely no support for the key algorithm", requestID) + + ctx.Providers.OpenIDConnect.WriteIntrospectionError(ctx, rw, errors.WithStack(fosite.ErrServerError.WithHint("Failed to lookup required information to perform this request.").WithDebugf("The JWK matching algorithm '%s' and key id '%s' could not be found.", alg, client.GetIntrospectionSignedResponseKeyID()))) + + return + } + + if jti, err = uuid.NewRandom(); err != nil { + ctx.Logger.WithError(err).Errorf("Introspection Request with id '%s' failed to generate a JTI", requestID) + + ctx.Providers.OpenIDConnect.WriteIntrospectionError(ctx, rw, errors.WithStack(fosite.ErrServerError.WithHint("Failed to lookup required information to perform this request.").WithDebugf("The JTI could not be generated for the Introspection JWT response type with error %+v.", err))) + + return + } + + headers := &jwt.Headers{ + Extra: map[string]any{ + oidc.JWTHeaderKeyIdentifier: jwk.KeyID(), + oidc.JWTHeaderKeyType: oidc.JWTHeaderTypeValueTokenIntrospectionJWT, + }, + } + + claims := map[string]any{ + oidc.ClaimJWTID: jti.String(), + oidc.ClaimIssuer: issuer, + oidc.ClaimIssuedAt: time.Now().UTC().Unix(), + oidc.ClaimTokenIntrospection: introspection, + } + + if aud != nil { + claims[oidc.ClaimAudience] = aud + } + + if token, _, err = jwk.Strategy().Generate(ctx, claims, headers); err != nil { + ctx.Logger.WithError(err).Errorf("Introspection Request with id '%s' failed to generate the Introspection JWT response", requestID) + + ctx.Providers.OpenIDConnect.WriteIntrospectionError(ctx, rw, errors.WithStack(fosite.ErrServerError.WithHint("Failed to generate the response.").WithDebugf("The Introspection JWT itself could not be generated with error %+v.", err))) + + return + } + + rw.Header().Set(fasthttp.HeaderContentType, "application/token-introspection+jwt; charset=utf-8") + rw.Header().Set(fasthttp.HeaderCacheControl, "no-store") + rw.Header().Set(fasthttp.HeaderPragma, "no-cache") + rw.WriteHeader(http.StatusOK) + + _, _ = rw.Write([]byte(token)) + } ctx.Logger.Debugf("Introspection Request with id '%s' was processed successfully", requestID) } |
