Skip to content

Commit a00c8fa

Browse files
committed
feat(model): add support for first and last name claims
• Introduces new fields for first_name and last_name in the web token • Implements fallback logic to prioritize first_name/last_name over given_name/family_name
1 parent bbdeb77 commit a00c8fa

File tree

4 files changed

+212
-3
lines changed

4 files changed

+212
-3
lines changed

jwt/model.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ func New(idToken string, signatureAlgorithms []jose.SignatureAlgorithm) (webToke
5252
webToken.IssuerAttributes = rawToken.IssuerAttributes
5353
webToken.Audiences = rawToken.getAudiences()
5454
webToken.Mail = rawToken.getMail()
55+
webToken.FirstName = rawToken.getFirstName()
56+
webToken.LastName = rawToken.getLastName()
5557

5658
return
5759
}

jwt/model_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,102 @@ func TestNew_DeserializationError(t *testing.T) {
4949
assert.Error(t, err)
5050
assert.Contains(t, err.Error(), "unable to deserialize claims")
5151
}
52+
53+
func TestNew_WithFirstNameAndLastName(t *testing.T) {
54+
claims := map[string]interface{}{
55+
"iss": "test-issuer",
56+
"sub": "test-subject",
57+
"first_name": "John",
58+
"last_name": "Doe",
59+
}
60+
61+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims))
62+
tokenString, err := token.SignedString(joseTestKey)
63+
assert.NoError(t, err)
64+
65+
webToken, err := New(tokenString, signatureAlgorithms)
66+
assert.NoError(t, err)
67+
assert.Equal(t, "John", webToken.FirstName)
68+
assert.Equal(t, "Doe", webToken.LastName)
69+
}
70+
71+
func TestNew_WithGivenNameAndFamilyName(t *testing.T) {
72+
claims := map[string]interface{}{
73+
"iss": "test-issuer",
74+
"sub": "test-subject",
75+
"given_name": "Jonathan",
76+
"family_name": "Smith",
77+
}
78+
79+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims))
80+
tokenString, err := token.SignedString(joseTestKey)
81+
assert.NoError(t, err)
82+
83+
webToken, err := New(tokenString, signatureAlgorithms)
84+
assert.NoError(t, err)
85+
assert.Equal(t, "Jonathan", webToken.FirstName)
86+
assert.Equal(t, "Smith", webToken.LastName)
87+
}
88+
89+
func TestNew_PreferFirstLastNameOverGivenFamilyName(t *testing.T) {
90+
claims := map[string]interface{}{
91+
"iss": "test-issuer",
92+
"sub": "test-subject",
93+
"first_name": "John",
94+
"last_name": "Doe",
95+
"given_name": "Jonathan",
96+
"family_name": "Smith",
97+
}
98+
99+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims))
100+
tokenString, err := token.SignedString(joseTestKey)
101+
assert.NoError(t, err)
102+
103+
webToken, err := New(tokenString, signatureAlgorithms)
104+
assert.NoError(t, err)
105+
// Should prefer first_name/last_name over given_name/family_name
106+
assert.Equal(t, "John", webToken.FirstName)
107+
assert.Equal(t, "Doe", webToken.LastName)
108+
}
109+
110+
func TestNew_FallbackToGivenFamilyNameWhenFirstLastEmpty(t *testing.T) {
111+
claims := map[string]interface{}{
112+
"iss": "test-issuer",
113+
"sub": "test-subject",
114+
"first_name": "",
115+
"last_name": "",
116+
"given_name": "Jonathan",
117+
"family_name": "Smith",
118+
}
119+
120+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims))
121+
tokenString, err := token.SignedString(joseTestKey)
122+
assert.NoError(t, err)
123+
124+
webToken, err := New(tokenString, signatureAlgorithms)
125+
assert.NoError(t, err)
126+
// Should fallback to given_name/family_name when first_name/last_name are empty
127+
assert.Equal(t, "Jonathan", webToken.FirstName)
128+
assert.Equal(t, "Smith", webToken.LastName)
129+
}
130+
131+
func TestNew_PartialFallback(t *testing.T) {
132+
claims := map[string]interface{}{
133+
"iss": "test-issuer",
134+
"sub": "test-subject",
135+
"first_name": "John",
136+
"last_name": "", // empty
137+
"given_name": "Jonathan",
138+
"family_name": "Smith",
139+
}
140+
141+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims))
142+
tokenString, err := token.SignedString(joseTestKey)
143+
assert.NoError(t, err)
144+
145+
webToken, err := New(tokenString, signatureAlgorithms)
146+
assert.NoError(t, err)
147+
// Should use first_name but fallback to family_name for last name
148+
assert.Equal(t, "John", webToken.FirstName)
149+
assert.Equal(t, "Smith", webToken.LastName)
150+
}

jwt/raw.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package jwt
22

33
type rawClaims struct {
4-
RawAudiences interface{} `json:"aud"` // RawAudiences could be a []string or string depending on the serialization in IdP site
5-
RawEmail string `json:"email,omitempty"`
6-
RawMail string `json:"mail,omitempty"`
4+
RawAudiences interface{} `json:"aud"` // RawAudiences could be a []string or string depending on the serialization in IdP site
5+
RawEmail string `json:"email,omitempty"`
6+
RawMail string `json:"mail,omitempty"`
7+
RawGivenName string `json:"given_name,omitempty"`
8+
RawFamilyName string `json:"family_name,omitempty"`
79
}
810

911
type rawWebToken struct {
@@ -20,6 +22,22 @@ func (r rawWebToken) getMail() (mail string) {
2022
return
2123
}
2224

25+
func (r rawWebToken) getLastName() (lastName string) {
26+
lastName = r.LastName
27+
if lastName == "" {
28+
lastName = r.RawFamilyName
29+
}
30+
return
31+
}
32+
33+
func (r rawWebToken) getFirstName() (firstName string) {
34+
firstName = r.FirstName
35+
if firstName == "" {
36+
firstName = r.RawGivenName
37+
}
38+
return
39+
}
40+
2341
func (r rawWebToken) getAudiences() (audiences []string) {
2442
switch audienceList := r.RawAudiences.(type) {
2543
case string:

jwt/raw_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,93 @@ func TestParseAudiencesString(t *testing.T) {
3737
assert.Contains(t, parsedAudiences, "audience1")
3838
assert.Equal(t, len(parsedAudiences), 1)
3939
}
40+
41+
func TestGetFirstName_PreferFirstName(t *testing.T) {
42+
token := rawWebToken{
43+
UserAttributes: UserAttributes{
44+
FirstName: "John",
45+
},
46+
rawClaims: rawClaims{
47+
RawGivenName: "Jonathan",
48+
},
49+
}
50+
51+
firstName := token.getFirstName()
52+
53+
assert.Equal(t, "John", firstName)
54+
}
55+
56+
func TestGetFirstName_FallbackToRawGivenName(t *testing.T) {
57+
token := rawWebToken{
58+
UserAttributes: UserAttributes{
59+
FirstName: "",
60+
},
61+
rawClaims: rawClaims{
62+
RawGivenName: "Jonathan",
63+
},
64+
}
65+
66+
firstName := token.getFirstName()
67+
68+
assert.Equal(t, "Jonathan", firstName)
69+
}
70+
71+
func TestGetFirstName_BothEmpty(t *testing.T) {
72+
token := rawWebToken{
73+
UserAttributes: UserAttributes{
74+
FirstName: "",
75+
},
76+
rawClaims: rawClaims{
77+
RawGivenName: "",
78+
},
79+
}
80+
81+
firstName := token.getFirstName()
82+
83+
assert.Equal(t, "", firstName)
84+
}
85+
86+
func TestGetLastName_PreferLastName(t *testing.T) {
87+
token := rawWebToken{
88+
UserAttributes: UserAttributes{
89+
LastName: "Doe",
90+
},
91+
rawClaims: rawClaims{
92+
RawFamilyName: "Smith",
93+
},
94+
}
95+
96+
lastName := token.getLastName()
97+
98+
assert.Equal(t, "Doe", lastName)
99+
}
100+
101+
func TestGetLastName_FallbackToRawFamilyName(t *testing.T) {
102+
token := rawWebToken{
103+
UserAttributes: UserAttributes{
104+
LastName: "",
105+
},
106+
rawClaims: rawClaims{
107+
RawFamilyName: "Smith",
108+
},
109+
}
110+
111+
lastName := token.getLastName()
112+
113+
assert.Equal(t, "Smith", lastName)
114+
}
115+
116+
func TestGetLastName_BothEmpty(t *testing.T) {
117+
token := rawWebToken{
118+
UserAttributes: UserAttributes{
119+
LastName: "",
120+
},
121+
rawClaims: rawClaims{
122+
RawFamilyName: "",
123+
},
124+
}
125+
126+
lastName := token.getLastName()
127+
128+
assert.Equal(t, "", lastName)
129+
}

0 commit comments

Comments
 (0)