Verify Apple ID token signature against JWKS
Fixes #25 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e3ef03ddcd
commit
d780a3403a
|
|
@ -2,11 +2,15 @@ package auth
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
|
|
@ -81,11 +85,61 @@ func (p *AppleProvider) getUserInfo(ctx context.Context, token *oauth2.Token) (*
|
|||
return nil, fmt.Errorf("missing id_token")
|
||||
}
|
||||
|
||||
// Parse without verification since we already got the token from Apple
|
||||
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||
parsed, _, err := parser.ParseUnverified(idToken, jwt.MapClaims{})
|
||||
// Fetch Apple's JWKS
|
||||
resp, err := http.Get("https://appleid.apple.com/auth/keys")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse id_token: %w", err)
|
||||
return nil, fmt.Errorf("fetch apple JWKS: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var jwks struct {
|
||||
Keys []struct {
|
||||
Kty string `json:"kty"`
|
||||
Kid string `json:"kid"`
|
||||
Use string `json:"use"`
|
||||
Alg string `json:"alg"`
|
||||
N string `json:"n"`
|
||||
E string `json:"e"`
|
||||
} `json:"keys"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil {
|
||||
return nil, fmt.Errorf("decode apple JWKS: %w", err)
|
||||
}
|
||||
|
||||
// Parse and verify the token
|
||||
parsed, err := jwt.Parse(idToken, func(t *jwt.Token) (interface{}, error) {
|
||||
kid, ok := t.Header["kid"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing kid header")
|
||||
}
|
||||
|
||||
for _, key := range jwks.Keys {
|
||||
if key.Kid == kid {
|
||||
// Decode RSA public key from JWK
|
||||
nBytes, err := base64.RawURLEncoding.DecodeString(key.N)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode key N: %w", err)
|
||||
}
|
||||
eBytes, err := base64.RawURLEncoding.DecodeString(key.E)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode key E: %w", err)
|
||||
}
|
||||
|
||||
e := 0
|
||||
for _, b := range eBytes {
|
||||
e = e*256 + int(b)
|
||||
}
|
||||
|
||||
return &rsa.PublicKey{
|
||||
N: new(big.Int).SetBytes(nBytes),
|
||||
E: e,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("key %s not found in JWKS", kid)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("verify id_token: %w", err)
|
||||
}
|
||||
|
||||
claims, ok := parsed.Claims.(jwt.MapClaims)
|
||||
|
|
@ -99,7 +153,7 @@ func (p *AppleProvider) getUserInfo(ctx context.Context, token *oauth2.Token) (*
|
|||
return &OAuthUserInfo{
|
||||
ProviderUserID: sub,
|
||||
Email: email,
|
||||
Name: email, // Apple may not provide name in id_token
|
||||
Name: email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue