-
Notifications
You must be signed in to change notification settings - Fork 1
/
introspector_jwt.go
110 lines (92 loc) · 3.02 KB
/
introspector_jwt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package oauth2
import (
"context"
"time"
"authelia.com/provider/oauth2"
"authelia.com/provider/oauth2/internal/consts"
"authelia.com/provider/oauth2/internal/errorsx"
"authelia.com/provider/oauth2/token/jwt"
)
type StatelessJWTValidator struct {
jwt.Signer
Config interface {
oauth2.ScopeStrategyProvider
}
}
func (v *StatelessJWTValidator) IntrospectToken(ctx context.Context, token string, tokenUse oauth2.TokenUse, accessRequest oauth2.AccessRequester, scopes []string) (oauth2.TokenUse, error) {
t, err := validateJWT(ctx, v.Signer, token)
if err != nil {
return "", err
}
if !IsJWTProfileAccessToken(t) {
return "", errorsx.WithStack(oauth2.ErrRequestUnauthorized.WithDebug("The provided token is not a valid RFC9068 JWT Profile Access Token as it is missing the header 'typ' value of 'at+jwt' "))
}
requester := AccessTokenJWTToRequest(t)
if err := matchScopes(v.Config.GetScopeStrategy(ctx), requester.GetGrantedScopes(), scopes); err != nil {
return oauth2.AccessToken, err
}
accessRequest.Merge(requester)
return oauth2.AccessToken, nil
}
// IsJWTProfileAccessToken validates a *jwt.Token is actually a RFC9068 JWT Profile Access Token by checking the
// relevant header as per https://datatracker.ietf.org/doc/html/rfc9068#section-2.1 which explicitly states that
// the header MUST include a typ of 'at+jwt' or 'application/at+jwt' with a preference of 'at+jwt'.
func IsJWTProfileAccessToken(token *jwt.Token) bool {
var (
raw any
typ string
ok bool
)
if raw, ok = token.Header[jwt.JWTHeaderKeyValueType]; !ok {
return false
}
typ, ok = raw.(string)
return ok && (typ == jwt.JWTHeaderTypeValueAccessTokenJWT || typ == "application/at+jwt")
}
// AccessTokenJWTToRequest tries to reconstruct oauth2.Request from a JWT.
func AccessTokenJWTToRequest(token *jwt.Token) oauth2.Requester {
mapClaims := token.Claims
claims := jwt.JWTClaims{}
claims.FromMapClaims(mapClaims)
requestedAt := claims.IssuedAt
requestedAtClaim, ok := mapClaims[consts.ClaimRequestedAt]
if ok {
switch rat := requestedAtClaim.(type) {
case float64:
requestedAt = time.Unix(int64(rat), 0).UTC()
case int64:
requestedAt = time.Unix(rat, 0).UTC()
}
}
clientId := ""
clientIdClaim, ok := mapClaims[consts.ClaimClientIdentifier]
if ok {
if id, ok := clientIdClaim.(string); ok {
clientId = id
}
}
return &oauth2.Request{
RequestedAt: requestedAt,
Client: &oauth2.DefaultClient{
ID: clientId,
},
// We do not really know which scopes were requested, so we set them to granted.
RequestedScope: claims.Scope,
GrantedScope: claims.Scope,
Session: &JWTSession{
JWTClaims: &claims,
JWTHeader: &jwt.Headers{
Extra: token.Header,
},
ExpiresAt: map[oauth2.TokenType]time.Time{
oauth2.AccessToken: claims.ExpiresAt,
},
Subject: claims.Subject,
},
// We do not really know which audiences were requested, so we set them to granted.
RequestedAudience: claims.Audience,
GrantedAudience: claims.Audience,
}
}