Skip to content

Commit 0951d18

Browse files
authored
Merge commit from fork
* Remove strings.Split and add parseToken function * review and add tests
1 parent c035977 commit 0951d18

File tree

2 files changed

+122
-3
lines changed

2 files changed

+122
-3
lines changed

jwt_test.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package jwt
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestSplitToken(t *testing.T) {
8+
t.Parallel()
9+
10+
tests := []struct {
11+
name string
12+
input string
13+
expected []string
14+
isValid bool
15+
}{
16+
{
17+
name: "valid token with three parts",
18+
input: "header.claims.signature",
19+
expected: []string{"header", "claims", "signature"},
20+
isValid: true,
21+
},
22+
{
23+
name: "invalid token with two parts only",
24+
input: "header.claims",
25+
expected: nil,
26+
isValid: false,
27+
},
28+
{
29+
name: "invalid token with one part only",
30+
input: "header",
31+
expected: nil,
32+
isValid: false,
33+
},
34+
{
35+
name: "invalid token with extra delimiter",
36+
input: "header.claims.signature.extra",
37+
expected: nil,
38+
isValid: false,
39+
},
40+
{
41+
name: "invalid empty token",
42+
input: "",
43+
expected: nil,
44+
isValid: false,
45+
},
46+
{
47+
name: "valid token with empty parts",
48+
input: "..signature",
49+
expected: []string{"", "", "signature"},
50+
isValid: true,
51+
},
52+
{
53+
// We are just splitting the token into parts, so we don't care about the actual values.
54+
// It is up to the caller to validate the parts.
55+
name: "valid token with all parts empty",
56+
input: "..",
57+
expected: []string{"", "", ""},
58+
isValid: true,
59+
},
60+
{
61+
name: "invalid token with just delimiters and extra part",
62+
input: "...",
63+
expected: nil,
64+
isValid: false,
65+
},
66+
{
67+
name: "invalid token with many delimiters",
68+
input: "header.claims.signature..................",
69+
expected: nil,
70+
isValid: false,
71+
},
72+
}
73+
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
parts, ok := splitToken(tt.input)
77+
if ok != tt.isValid {
78+
t.Errorf("expected %t, got %t", tt.isValid, ok)
79+
}
80+
if ok {
81+
for i, part := range tt.expected {
82+
if parts[i] != part {
83+
t.Errorf("expected %s, got %s", part, parts[i])
84+
}
85+
}
86+
}
87+
})
88+
}
89+
}

parser.go

+33-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"strings"
99
)
1010

11+
const tokenDelimiter = "."
12+
1113
type Parser struct {
1214
// If populated, only these methods will be considered valid.
1315
validMethods []string
@@ -136,9 +138,10 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
136138
// It's only ever useful in cases where you know the signature is valid (since it has already
137139
// been or will be checked elsewhere in the stack) and you want to extract values from it.
138140
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
139-
parts = strings.Split(tokenString, ".")
140-
if len(parts) != 3 {
141-
return nil, parts, newError("token contains an invalid number of segments", ErrTokenMalformed)
141+
var ok bool
142+
parts, ok = splitToken(tokenString)
143+
if !ok {
144+
return nil, nil, newError("token contains an invalid number of segments", ErrTokenMalformed)
142145
}
143146

144147
token = &Token{Raw: tokenString}
@@ -196,6 +199,33 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
196199
return token, parts, nil
197200
}
198201

202+
// splitToken splits a token string into three parts: header, claims, and signature. It will only
203+
// return true if the token contains exactly two delimiters and three parts. In all other cases, it
204+
// will return nil parts and false.
205+
func splitToken(token string) ([]string, bool) {
206+
parts := make([]string, 3)
207+
header, remain, ok := strings.Cut(token, tokenDelimiter)
208+
if !ok {
209+
return nil, false
210+
}
211+
parts[0] = header
212+
claims, remain, ok := strings.Cut(remain, tokenDelimiter)
213+
if !ok {
214+
return nil, false
215+
}
216+
parts[1] = claims
217+
// One more cut to ensure the signature is the last part of the token and there are no more
218+
// delimiters. This avoids an issue where malicious input could contain additional delimiters
219+
// causing unecessary overhead parsing tokens.
220+
signature, _, unexpected := strings.Cut(remain, tokenDelimiter)
221+
if unexpected {
222+
return nil, false
223+
}
224+
parts[2] = signature
225+
226+
return parts, true
227+
}
228+
199229
// DecodeSegment decodes a JWT specific base64url encoding. This function will
200230
// take into account whether the [Parser] is configured with additional options,
201231
// such as [WithStrictDecoding] or [WithPaddingAllowed].

0 commit comments

Comments
 (0)