Skip to content

Commit 7261e15

Browse files
auth: add AllowMissingExpiration option to RequireBearerTokenOptions
Some IdPs emit session-bound bearer tokens that do not carry a standalone `exp` claim — the token's lifetime is bounded by an external session and is not advertised in-band. Resource servers integrating with such IdPs need to opt in to validating the rest of the token (scopes, signature via the verifier callback) without requiring the expiration field to be present. Adds an AllowMissingExpiration bool to RequireBearerTokenOptions. Default false preserves the existing strict behaviour. When true, a TokenInfo with a zero Expiration is accepted; non-zero expirations are still checked for elapsed validity. Extends TestVerify with a "no expiration with AllowMissingExpiration accepts" case mirroring the existing strict-reject case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bf69179 commit 7261e15

2 files changed

Lines changed: 24 additions & 3 deletions

File tree

auth/auth.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ type RequireBearerTokenOptions struct {
4747
ResourceMetadataURL string
4848
// The required scopes.
4949
Scopes []string
50+
// AllowMissingExpiration opts the middleware out of the
51+
// `tokenInfo.Expiration.IsZero()` reject. Default false preserves the
52+
// existing strict behaviour (every TokenInfo must carry an Expiration).
53+
//
54+
// Some IdPs emit session-bound bearer tokens that do not carry a standalone
55+
// `exp` claim — the token's lifetime is bounded by an external session and
56+
// is not advertised in-band. Resource servers integrating with such IdPs
57+
// need to opt in to validating the rest of the token (scopes, signature
58+
// via the verifier callback, etc.) without requiring the expiration field
59+
// to be present.
60+
//
61+
// When enabled, the verifier is still responsible for any session-level
62+
// validity check it can perform; this option only relaxes the middleware's
63+
// own expiration enforcement.
64+
AllowMissingExpiration bool
5065
}
5166

5267
type tokenInfoKey struct{}
@@ -131,9 +146,10 @@ func verify(req *http.Request, verifier TokenVerifier, opts *RequireBearerTokenO
131146

132147
// Check expiration.
133148
if tokenInfo.Expiration.IsZero() {
134-
return nil, "token missing expiration", http.StatusUnauthorized
135-
}
136-
if tokenInfo.Expiration.Before(time.Now()) {
149+
if opts == nil || !opts.AllowMissingExpiration {
150+
return nil, "token missing expiration", http.StatusUnauthorized
151+
}
152+
} else if tokenInfo.Expiration.Before(time.Now()) {
137153
return nil, "token expired", http.StatusUnauthorized
138154
}
139155
return tokenInfo, "", 0

auth/auth_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ func TestVerify(t *testing.T) {
6262
"no expiration", nil, "Bearer noexp",
6363
"token missing expiration", 401,
6464
},
65+
{
66+
"no expiration with AllowMissingExpiration accepts",
67+
&RequireBearerTokenOptions{AllowMissingExpiration: true}, "Bearer noexp",
68+
"", 0,
69+
},
6570
{
6671
"expired", nil, "Bearer expired",
6772
"token expired", 401,

0 commit comments

Comments
 (0)