• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

golang-jwt / jwt / 21484616490

29 Jan 2026 03:41PM UTC coverage: 70.172%. Remained the same
21484616490

Pull #485

github

web-flow
Merge 16a989806 into 7ceae619e
Pull Request #485: Feat: MapClaims Key Type-Safety

9 of 10 new or added lines in 2 files covered. (90.0%)

10 existing lines in 2 files now uncovered.

981 of 1398 relevant lines covered (70.17%)

15.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.48
/parser.go
1
package jwt
2

3
import (
4
        "bytes"
5
        "encoding/base64"
6
        "encoding/json"
7
        "fmt"
8
        "strings"
9
)
10

11
const tokenDelimiter = "."
12

13
type Parser struct {
14
        // If populated, only these methods will be considered valid.
15
        validMethods []string
16

17
        // Use JSON Number format in JSON decoder.
18
        useJSONNumber bool
19

20
        // Skip claims validation during token parsing.
21
        skipClaimsValidation bool
22

23
        validator *Validator
24

25
        decodeStrict bool
26

27
        decodePaddingAllowed bool
28
}
29

30
// NewParser creates a new Parser with the specified options
31
func NewParser(options ...ParserOption) *Parser {
124✔
32
        p := &Parser{
124✔
33
                validator: &Validator{},
124✔
34
        }
124✔
35

124✔
36
        // Loop through our parsing options and apply them
124✔
37
        for _, option := range options {
194✔
38
                option(p)
70✔
39
        }
70✔
40

41
        return p
124✔
42
}
43

44
// Parse parses, validates, verifies the signature and returns the parsed token.
45
// keyFunc will receive the parsed token and should return the key for validating.
46
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
2✔
47
        return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
2✔
48
}
2✔
49

50
// ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object implementing the Claims
51
// interface. This provides default values which can be overridden and allows a caller to use their own type, rather
52
// than the default MapClaims implementation of Claims.
53
//
54
// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims),
55
// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the
56
// proper memory for it before passing in the overall claims, otherwise you might run into a panic.
57
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
57✔
58
        token, parts, err := p.ParseUnverified(tokenString, claims)
57✔
59
        if err != nil {
66✔
60
                return token, err
9✔
61
        }
9✔
62

63
        // Verify signing method is in the required set
64
        if p.validMethods != nil {
53✔
65
                var signingMethodValid = false
5✔
66
                var alg = token.Method.Alg()
5✔
67
                for _, m := range p.validMethods {
12✔
68
                        if m == alg {
10✔
69
                                signingMethodValid = true
3✔
70
                                break
3✔
71
                        }
72
                }
73
                if !signingMethodValid {
7✔
74
                        // signing method is not in the listed set
2✔
75
                        return token, newError(fmt.Sprintf("signing method %v is invalid", alg), ErrTokenSignatureInvalid)
2✔
76
                }
2✔
77
        }
78

79
        // Lookup key(s)
80
        if keyFunc == nil {
47✔
81
                // keyFunc was not provided.  short circuiting validation
1✔
82
                return token, newError("no keyfunc was provided", ErrTokenUnverifiable)
1✔
83
        }
1✔
84

85
        got, err := keyFunc(token)
45✔
86
        if err != nil {
46✔
87
                return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err)
1✔
88
        }
1✔
89

90
        // Join together header and claims in order to verify them with the signature
91
        text := strings.Join(parts[0:2], ".")
44✔
92
        switch have := got.(type) {
44✔
93
        case VerificationKeySet:
5✔
94
                if len(have.Keys) == 0 {
6✔
95
                        return token, newError("keyfunc returned empty verification key set", ErrTokenUnverifiable)
1✔
96
                }
1✔
97

98
                // Iterate through keys and verify signature, skipping the rest when a match is found.
99
                // Return the last error if no match is found.
100
                for _, key := range have.Keys {
10✔
101
                        if err = token.Method.Verify(text, token.Signature, key); err == nil {
9✔
102
                                break
3✔
103
                        }
104
                }
105
        default:
39✔
106
                err = token.Method.Verify(text, token.Signature, have)
39✔
107
        }
108
        if err != nil {
48✔
109
                return token, newError("", ErrTokenSignatureInvalid, err)
5✔
110
        }
5✔
111

112
        // Validate Claims
113
        if !p.skipClaimsValidation {
65✔
114
                // Make sure we have at least a default validator
27✔
115
                if p.validator == nil {
28✔
116
                        p.validator = NewValidator()
1✔
117
                }
1✔
118

119
                if err := p.validator.Validate(claims); err != nil {
36✔
120
                        return token, newError("", ErrTokenInvalidClaims, err)
9✔
121
                }
9✔
122
        }
123

124
        // No errors so far, token is valid.
125
        token.Valid = true
29✔
126

29✔
127
        return token, nil
29✔
128
}
129

130
// ParseUnverified parses the token but does not validate the signature.
131
//
132
// WARNING: Don't use this method unless you know what you're doing.
133
//
134
// It's only ever useful in cases where you know the signature is valid (since it has already
135
// been or will be checked elsewhere in the stack) and you want to extract values from it.
136
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
88✔
137
        var ok bool
88✔
138
        parts, ok = splitToken(tokenString)
88✔
139
        if !ok {
89✔
140
                return nil, nil, newError("token contains an invalid number of segments", ErrTokenMalformed)
1✔
141
        }
1✔
142

143
        token = &Token{Raw: tokenString}
87✔
144

87✔
145
        // Parse Header
87✔
146
        var headerBytes []byte
87✔
147
        if headerBytes, err = p.DecodeSegment(parts[0]); err != nil {
89✔
148
                return token, parts, newError("could not base64 decode header", ErrTokenMalformed, err)
2✔
149
        }
2✔
150
        if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
85✔
UNCOV
151
                return token, parts, newError("could not JSON decode header", ErrTokenMalformed, err)
×
UNCOV
152
        }
×
153

154
        // Parse Claims
155
        token.Claims = claims
85✔
156

85✔
157
        claimBytes, err := p.DecodeSegment(parts[1])
85✔
158
        if err != nil {
86✔
159
                return token, parts, newError("could not base64 decode claim", ErrTokenMalformed, err)
1✔
160
        }
1✔
161

162
        // If `useJSONNumber` is enabled then we must use *json.Decoder to decode
163
        // the claims. However, this comes with a performance penalty so only use
164
        // it if we must and, otherwise, simple use json.Unmarshal.
165
        if !p.useJSONNumber {
150✔
166
                // JSON Unmarshal. Special case for map type to avoid weird pointer behavior.
66✔
167
                if c, ok := token.Claims.(MapClaims); ok {
118✔
168
                        err = json.Unmarshal(claimBytes, &c)
52✔
169
                } else {
66✔
170
                        err = json.Unmarshal(claimBytes, &claims)
14✔
171
                }
14✔
172
        } else {
18✔
173
                dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
18✔
174
                dec.UseNumber()
18✔
175
                // JSON Decode. Special case for map type to avoid weird pointer behavior.
18✔
176
                if c, ok := token.Claims.(MapClaims); ok {
28✔
177
                        err = dec.Decode(&c)
10✔
178
                } else {
18✔
179
                        err = dec.Decode(&claims)
8✔
180
                }
8✔
181
        }
182
        if err != nil {
87✔
183
                return token, parts, newError("could not JSON decode claim", ErrTokenMalformed, err)
3✔
184
        }
3✔
185

186
        // Lookup signature method
187
        if method, ok := token.Header["alg"].(string); ok {
162✔
188
                if token.Method = GetSigningMethod(method); token.Method == nil {
81✔
UNCOV
189
                        return token, parts, newError("signing method (alg) is unavailable", ErrTokenUnverifiable)
×
UNCOV
190
                }
×
UNCOV
191
        } else {
×
UNCOV
192
                return token, parts, newError("signing method (alg) is unspecified", ErrTokenUnverifiable)
×
193
        }
×
194

195
        // Parse token signature
196
        token.Signature, err = p.DecodeSegment(parts[2])
81✔
197
        if err != nil {
83✔
198
                return token, parts, newError("could not base64 decode signature", ErrTokenMalformed, err)
2✔
199
        }
2✔
200

201
        return token, parts, nil
79✔
202
}
203

204
// splitToken splits a token string into three parts: header, claims, and signature. It will only
205
// return true if the token contains exactly two delimiters and three parts. In all other cases, it
206
// will return nil parts and false.
207
func splitToken(token string) ([]string, bool) {
97✔
208
        parts := make([]string, 3)
97✔
209
        header, remain, ok := strings.Cut(token, tokenDelimiter)
97✔
210
        if !ok {
100✔
211
                return nil, false
3✔
212
        }
3✔
213
        parts[0] = header
94✔
214
        claims, remain, ok := strings.Cut(remain, tokenDelimiter)
94✔
215
        if !ok {
95✔
216
                return nil, false
1✔
217
        }
1✔
218
        parts[1] = claims
93✔
219
        // One more cut to ensure the signature is the last part of the token and there are no more
93✔
220
        // delimiters. This avoids an issue where malicious input could contain additional delimiters
93✔
221
        // causing unnecessary overhead parsing tokens.
93✔
222
        signature, _, unexpected := strings.Cut(remain, tokenDelimiter)
93✔
223
        if unexpected {
96✔
224
                return nil, false
3✔
225
        }
3✔
226
        parts[2] = signature
90✔
227

90✔
228
        return parts, true
90✔
229
}
230

231
// DecodeSegment decodes a JWT specific base64url encoding. This function will
232
// take into account whether the [Parser] is configured with additional options,
233
// such as [WithStrictDecoding] or [WithPaddingAllowed].
234
func (p *Parser) DecodeSegment(seg string) ([]byte, error) {
288✔
235
        encoding := base64.RawURLEncoding
288✔
236

288✔
237
        if p.decodePaddingAllowed {
309✔
238
                if l := len(seg) % 4; l > 0 {
23✔
239
                        seg += strings.Repeat("=", 4-l)
2✔
240
                }
2✔
241
                encoding = base64.URLEncoding
21✔
242
        }
243

244
        if p.decodeStrict {
300✔
245
                encoding = encoding.Strict()
12✔
246
        }
12✔
247
        return encoding.DecodeString(seg)
288✔
248
}
249

250
// Parse parses, validates, verifies the signature and returns the parsed token.
251
// keyFunc will receive the parsed token and should return the cryptographic key
252
// for verifying the signature. The caller is strongly encouraged to set the
253
// WithValidMethods option to validate the 'alg' claim in the token matches the
254
// expected algorithm. For more details about the importance of validating the
255
// 'alg' claim, see
256
// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
257
func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
2✔
258
        return NewParser(options...).Parse(tokenString, keyFunc)
2✔
259
}
2✔
260

261
// ParseWithClaims is a shortcut for NewParser().ParseWithClaims().
262
//
263
// Note: If you provide a custom claim implementation that embeds one of the
264
// standard claims (such as RegisteredClaims), make sure that a) you either
265
// embed a non-pointer version of the claims or b) if you are using a pointer,
266
// allocate the proper memory for it before passing in the overall claims,
267
// otherwise you might run into a panic.
268
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
4✔
269
        return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc)
4✔
270
}
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc