Skip to content

Commit

Permalink
Fix issue with id_token unmarshalling (#538)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaronius committed Feb 16, 2024
1 parent ce547e9 commit e9e5240
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 6 deletions.
43 changes: 38 additions & 5 deletions providers/apple/session.go
Expand Up @@ -48,10 +48,10 @@ func (s Session) Marshal() string {

type IDTokenClaims struct {
jwt.StandardClaims
AccessTokenHash string `json:"at_hash"`
AuthTime int `json:"auth_time"`
Email string `json:"email"`
IsPrivateEmail bool `json:"is_private_email,string"`
AccessTokenHash string `json:"at_hash"`
AuthTime int `json:"auth_time"`
Email string `json:"email"`
IsPrivateEmail BoolString `json:"is_private_email"`
}

func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
Expand Down Expand Up @@ -123,7 +123,7 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string,
s.ID = ID{
Sub: idToken.Claims.(*IDTokenClaims).Subject,
Email: idToken.Claims.(*IDTokenClaims).Email,
IsPrivateEmail: idToken.Claims.(*IDTokenClaims).IsPrivateEmail,
IsPrivateEmail: idToken.Claims.(*IDTokenClaims).IsPrivateEmail.Value(),
}
}

Expand All @@ -133,3 +133,36 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string,
func (s Session) String() string {
return s.Marshal()
}

// BoolString is a type that can be unmarshalled from a JSON field that can be either a boolean or a string.
// It is used to unmarshal some fields in the Apple ID token that can be sent as either boolean or string.
// See https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple#3383773
type BoolString struct {
BoolValue bool
StringValue string
IsValidBool bool
}

func (bs *BoolString) UnmarshalJSON(data []byte) error {
var b bool
if err := json.Unmarshal(data, &b); err == nil {
bs.BoolValue = b
bs.IsValidBool = true
return nil
}

var s string
if err := json.Unmarshal(data, &s); err == nil {
bs.StringValue = s
return nil
}

return errors.New("json field can be either boolean or string")
}

func (bs *BoolString) Value() bool {
if bs.IsValidBool {
return bs.BoolValue
}
return bs.StringValue == "true"
}
46 changes: 45 additions & 1 deletion providers/apple/session_test.go
@@ -1,10 +1,12 @@
package apple

import (
"encoding/json"
"testing"

"github.com/markbates/goth"
"github.com/stretchr/testify/assert"

"github.com/markbates/goth"
)

func Test_Implements_Session(t *testing.T) {
Expand Down Expand Up @@ -45,3 +47,45 @@ func Test_String(t *testing.T) {

a.Equal(s.String(), s.Marshal())
}

func TestIDTokenClaimsUnmarshal(t *testing.T) {
t.Parallel()
a := assert.New(t)

cases := []struct {
name string
idToken string
expectedClaims IDTokenClaims
}{
{
name: "'is_private_email' claim is a string",
idToken: `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresAt":"0001-01-01T00:00:00Z","sub":"","email":"test-email@privaterelay.appleid.com","is_private_email":"true"}`,
expectedClaims: IDTokenClaims{
Email: "test-email@privaterelay.appleid.com",
IsPrivateEmail: BoolString{
StringValue: "true",
},
},
},
{
name: "'is_private_email' claim is a boolean",
idToken: `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresAt":"0001-01-01T00:00:00Z","sub":"","email":"test-email@privaterelay.appleid.com","is_private_email":true}`,
expectedClaims: IDTokenClaims{
Email: "test-email@privaterelay.appleid.com",
IsPrivateEmail: BoolString{
BoolValue: true,
IsValidBool: true,
},
},
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
idTokenClaims := IDTokenClaims{}
err := json.Unmarshal([]byte(c.idToken), &idTokenClaims)
a.NoError(err)
a.Equal(idTokenClaims, c.expectedClaims)
})
}
}

0 comments on commit e9e5240

Please sign in to comment.