{"record":{"id":"2e170871312ed94d","repo":"oauth2-proxy/oauth2-proxy","slug":"forbidden","errorCode":null,"errorMessage":"Forbidden","messagePattern":"Forbidden","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"pkg/middleware/jwt_session.go","lineNumber":56,"sourceCode":"// will be loaded and the request will be passed to the next handler.\n// Or if the JWT is invalid and denyInvalidJWTs, return 403 now.\n// If a session was loaded by a previous handler, it will not be replaced.\nfunc (j *jwtSessionLoader) loadSession(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {\n\t\tscope := middlewareapi.GetRequestScope(req)\n\t\t// If scope is nil, this will panic.\n\t\t// A scope should always be injected before this handler is called.\n\t\tif scope.Session != nil {\n\t\t\t// The session was already loaded, pass to the next handler\n\t\t\tnext.ServeHTTP(rw, req)\n\t\t\treturn\n\t\t}\n\n\t\tsession, err := j.getJwtSession(req)\n\t\tif err != nil {\n\t\t\tlogger.Errorf(\"Error retrieving session from token in Authorization header: %v\", err)\n\t\t\tif j.denyInvalidJWTs {\n\t\t\t\thttp.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// Add the session to the scope if it was found\n\t\tscope.Session = session\n\t\tnext.ServeHTTP(rw, req)\n\t})\n}\n\n// getJwtSession loads a session based on a JWT token in the authorization header.\n// (see the config options skip-jwt-bearer-tokens, extra-jwt-issuers, and bearer-token-login-fallback)\nfunc (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.SessionState, error) {\n\tauth := req.Header.Get(\"Authorization\")\n\tif auth == \"\" {\n\t\t// No auth header provided, so don't attempt to load a session\n\t\treturn nil, nil\n\t}","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/oauth2-proxy/oauth2-proxy/blob/33c2eb92dea78204f7a18bc2dfdbccc220f39257/pkg/middleware/jwt_session.go#L38-L74","documentation":"When a request carries a JWT in the Authorization header but the session cannot be retrieved from it (invalid, expired, or malformed token), and denyInvalidJWTs is enabled, the middleware responds with HTTP 403 Forbidden and stops the request chain. Without the deny flag the request would proceed unauthenticated instead.","triggerScenarios":"Sending a request whose Authorization header JWT fails validation (bad signature, expired, wrong issuer/audience, malformed) while the JWT session middleware is configured with deny invalid JWTs enabled.","commonSituations":"Expired or rotated signing keys after a key change; clock skew between client and server; token from a different issuer than configured; copy-pasted tokens with extra whitespace or 'Bearer' casing issues.","solutions":["Obtain a fresh, valid JWT from the configured issuer and resend with 'Authorization: Bearer <token>'","Verify the JWT's signature key, issuer, and audience match the middleware configuration","Check server logs for 'Error retrieving session from token in Authorization header' to see the underlying cause","If tokens are only sometimes invalid and should not be hard-denied, disable the deny-invalid-JWTs option"],"exampleFix":"// client side\n// before\nAuthorization: eyJhbGciOi...\n// after\nAuthorization: Bearer eyJhbGciOi...  // with a valid, unexpired token","handlingStrategy":"try-catch","validationCode":"parts := strings.SplitN(authz, \" \", 2)\nvalid := len(parts) == 2 && parts[0] == \"Bearer\" && len(parts[1]) > 0\nif !valid { /* refresh or obtain a token before the request */ }","typeGuard":"func hasBearerToken(h http.Header) (string, bool) {\n    const p = \"Bearer \"\n    v := h.Get(\"Authorization\")\n    return strings.TrimPrefix(v, p), strings.HasPrefix(v, p)\n}","tryCatchPattern":"// parse and validate the JWT client-side before sending:\ntok, err := jwt.Parse(token, keyfunc)\nif err != nil || !tok.Valid {\n    token = refreshToken()\n}","preventionTips":["Refresh tokens proactively before expiry; add clock-skew tolerance","Confirm issuer/audience configuration matches what your IdP issues","Always use the exact 'Bearer <token>' header format","Handle 403 by re-authenticating rather than retrying with the same token"],"tags":["jwt","authentication","forbidden","http-403"],"backgroundTag":"jwt-token-expired","analyzedSha":"33c2eb92dea78204f7a18bc2dfdbccc220f39257","analyzedAt":"2026-09-06T08:51:53.077Z","contentChangedAt":"2026-09-06T08:51:53.077Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}