Skip to content

Commit f3548c0

Browse files
committed
feat: Enhance GoogleProvider to support OpenID
1 parent 145e080 commit f3548c0

File tree

2 files changed

+84
-7
lines changed

2 files changed

+84
-7
lines changed

internal/auth/providers/google_provider.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package providers
33

44
import (
55
"context"
6+
"crypto/rsa"
67
"encoding/json"
8+
"errors"
9+
"fmt"
710
"net/http"
811

12+
"github.com/golang-jwt/jwt/v4"
913
"golang.org/x/oauth2"
1014
"golang.org/x/oauth2/google"
1115
)
@@ -20,7 +24,7 @@ func NewGoogleProvider(clientID, clientSecret, redirectURL string) *GoogleProvid
2024
ClientID: clientID,
2125
ClientSecret: clientSecret,
2226
RedirectURL: redirectURL,
23-
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
27+
Scopes: []string{"openid", "profile", "email"},
2428
Endpoint: google.Endpoint,
2529
},
2630
}
@@ -37,9 +41,14 @@ func (g *GoogleProvider) ExchangeCode(ctx context.Context, code string) (TokenRe
3741
if err != nil {
3842
return TokenResponse{}, err
3943
}
44+
idToken, ok := token.Extra("id_token").(string)
45+
if !ok {
46+
return TokenResponse{}, fmt.Errorf("ID Token not found in response")
47+
}
4048
return TokenResponse{
4149
AccessToken: token.AccessToken,
4250
RefreshToken: token.RefreshToken,
51+
IDToken: idToken,
4352
ExpiresIn: int(token.Expiry.Sub(token.Expiry).Seconds()),
4453
}, nil
4554
}
@@ -64,3 +73,70 @@ func (g *GoogleProvider) GetUserInfo(accessToken string) (UserInfo, error) {
6473
}
6574
return userInfo, nil
6675
}
76+
77+
func (g *GoogleProvider) GetUserInfoFromIDToken(idToken string) (UserInfo, error) {
78+
var userInfo UserInfo
79+
80+
// Parse and validate the ID token
81+
parsedToken, err := jwt.Parse(idToken, func(token *jwt.Token) (interface{}, error) {
82+
return getGooglePublicKey()
83+
})
84+
if err != nil {
85+
return userInfo, err
86+
}
87+
88+
// Check if the token is valid and has the expected claims
89+
claims, ok := parsedToken.Claims.(jwt.MapClaims)
90+
if !ok {
91+
return userInfo, errors.New("invalid claims in ID Token")
92+
}
93+
94+
// Extract user info from the claims
95+
userInfo.Email = claims["email"].(string)
96+
userInfo.Name = claims["name"].(string)
97+
userInfo.PictureURL = claims["picture"].(string)
98+
99+
return userInfo, nil
100+
}
101+
102+
func getGooglePublicKey() (*rsa.PublicKey, error) {
103+
const certsURL = "https://www.googleapis.com/oauth2/v3/certs"
104+
resp, err := http.Get(certsURL)
105+
if err != nil {
106+
return nil, err
107+
}
108+
defer resp.Body.Close()
109+
110+
var certs map[string]interface{}
111+
if err := json.NewDecoder(resp.Body).Decode(&certs); err != nil {
112+
return nil, err
113+
}
114+
115+
keyID := "some-key-id"
116+
if keyData, ok := certs[keyID]; ok {
117+
keyDataMap, ok := keyData.(map[string]interface{})
118+
if !ok {
119+
return nil, fmt.Errorf("invalid key data")
120+
}
121+
122+
publicKeyData, ok := keyDataMap["x5c"].([]interface{})
123+
if !ok || len(publicKeyData) == 0 {
124+
return nil, fmt.Errorf("x5c field missing or invalid")
125+
}
126+
127+
certData := publicKeyData[0].(string)
128+
certBytes, err := json.Marshal(certData)
129+
if err != nil {
130+
return nil, err
131+
}
132+
133+
publicKey, err := jwt.ParseRSAPublicKeyFromPEM(certBytes)
134+
if err != nil {
135+
return nil, fmt.Errorf("unable to parse public key: %w", err)
136+
}
137+
138+
return publicKey, nil
139+
}
140+
141+
return nil, fmt.Errorf("key not found")
142+
}

internal/auth/providers/oauth_provider.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ type OAuthProvider interface {
1111

1212
// TokenResponse represents the response after exchanging the authorization code.
1313
type TokenResponse struct {
14-
AccessToken string
15-
RefreshToken string
16-
ExpiresIn int
14+
AccessToken string `json:"access_token"`
15+
RefreshToken string `json:"refresh_token"`
16+
IDToken string `json:"id_token"`
17+
ExpiresIn int `json:"expires_in"`
1718
}
1819

1920
// UserInfo represents the user information retrieved from the provider.
2021
type UserInfo struct {
21-
Email string
22-
Name string
23-
PictureURL string
22+
Email string `json:"email"`
23+
Name string `json:"name"`
24+
PictureURL string `json:"picture"`
2425
}

0 commit comments

Comments
 (0)