Skip to content

Commit 6ca37a0

Browse files
committed
feat: add oidc for google login
1 parent a9cf301 commit 6ca37a0

File tree

6 files changed

+96
-39
lines changed

6 files changed

+96
-39
lines changed

server/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.16
44

55
require (
66
github.com/99designs/gqlgen v0.13.0
7+
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
78
github.com/gin-contrib/location v0.0.2 // indirect
89
github.com/gin-gonic/gin v1.7.2
910
github.com/go-playground/validator/v10 v10.8.0 // indirect

server/go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
109109
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
110110
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
111111
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
112+
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
113+
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
112114
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
113115
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
114116
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@@ -737,6 +739,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
737739
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
738740
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
739741
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
742+
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
740743
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
741744
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
742745
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -1100,6 +1103,8 @@ gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbR
11001103
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
11011104
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
11021105
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
1106+
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
1107+
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
11031108
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
11041109
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
11051110
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=

server/handlers/oauthCallback.go

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package handlers
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"io/ioutil"
@@ -15,36 +16,50 @@ import (
1516
"github.com/authorizerdev/authorizer/server/oauth"
1617
"github.com/authorizerdev/authorizer/server/session"
1718
"github.com/authorizerdev/authorizer/server/utils"
19+
"github.com/coreos/go-oidc/v3/oidc"
1820
"github.com/gin-gonic/gin"
1921
"golang.org/x/oauth2"
2022
)
2123

2224
func processGoogleUserInfo(code string) (db.User, error) {
2325
user := db.User{}
24-
token, err := oauth.OAuthProvider.GoogleConfig.Exchange(oauth2.NoContext, code)
26+
ctx := context.Background()
27+
oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code)
2528
if err != nil {
2629
return user, fmt.Errorf("invalid google exchange code: %s", err.Error())
2730
}
28-
client := oauth.OAuthProvider.GoogleConfig.Client(oauth2.NoContext, token)
29-
response, err := client.Get(constants.GoogleUserInfoURL)
30-
if err != nil {
31-
return user, err
31+
32+
verifier := oauth.OIDCProviders.GoogleOIDC.Verifier(&oidc.Config{ClientID: oauth.OAuthProviders.GoogleConfig.ClientID})
33+
34+
// Extract the ID Token from OAuth2 token.
35+
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
36+
if !ok {
37+
return user, fmt.Errorf("unable to extract id_token")
3238
}
3339

34-
defer response.Body.Close()
35-
body, err := ioutil.ReadAll(response.Body)
40+
// Parse and verify ID Token payload.
41+
idToken, err := verifier.Verify(ctx, rawIDToken)
3642
if err != nil {
37-
return user, fmt.Errorf("failed to read google response body: %s", err.Error())
43+
return user, fmt.Errorf("unable to verify id_token:", err.Error())
3844
}
3945

40-
userRawData := make(map[string]string)
41-
json.Unmarshal(body, &userRawData)
46+
// Extract custom claims
47+
var claims struct {
48+
Email string `json:"email"`
49+
Picture string `json:"picture"`
50+
GivenName string `json:"given_name"`
51+
FamilyName string `json:"family_name"`
52+
Verified bool `json:"email_verified"`
53+
}
54+
if err := idToken.Claims(&claims); err != nil {
55+
return user, fmt.Errorf("unable to extract claims")
56+
}
4257

4358
user = db.User{
44-
FirstName: userRawData["given_name"],
45-
LastName: userRawData["family_name"],
46-
Image: userRawData["picture"],
47-
Email: userRawData["email"],
59+
FirstName: claims.GivenName,
60+
LastName: claims.FamilyName,
61+
Image: claims.Picture,
62+
Email: claims.Email,
4863
EmailVerifiedAt: time.Now().Unix(),
4964
}
5065

@@ -53,7 +68,7 @@ func processGoogleUserInfo(code string) (db.User, error) {
5368

5469
func processGithubUserInfo(code string) (db.User, error) {
5570
user := db.User{}
56-
token, err := oauth.OAuthProvider.GithubConfig.Exchange(oauth2.NoContext, code)
71+
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
5772
if err != nil {
5873
return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
5974
}
@@ -102,7 +117,7 @@ func processGithubUserInfo(code string) (db.User, error) {
102117

103118
func processFacebookUserInfo(code string) (db.User, error) {
104119
user := db.User{}
105-
token, err := oauth.OAuthProvider.FacebookConfig.Exchange(oauth2.NoContext, code)
120+
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
106121
if err != nil {
107122
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
108123
}

server/handlers/oauthLogin.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,46 @@ func OAuthLoginHandler() gin.HandlerFunc {
4848
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
4949

5050
provider := c.Param("oauth_provider")
51-
51+
isProviderConfigured := true
5252
switch provider {
5353
case enum.Google.String():
54+
if oauth.OAuthProviders.GoogleConfig == nil {
55+
isProviderConfigured = false
56+
break
57+
}
5458
session.SetSocailLoginState(oauthStateString, enum.Google.String())
5559
// during the init of OAuthProvider authorizer url might be empty
56-
oauth.OAuthProvider.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google"
57-
url := oauth.OAuthProvider.GoogleConfig.AuthCodeURL(oauthStateString)
60+
oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google"
61+
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
5862
c.Redirect(http.StatusTemporaryRedirect, url)
5963
case enum.Github.String():
64+
if oauth.OAuthProviders.GithubConfig == nil {
65+
isProviderConfigured = false
66+
break
67+
}
6068
session.SetSocailLoginState(oauthStateString, enum.Github.String())
61-
oauth.OAuthProvider.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github"
62-
url := oauth.OAuthProvider.GithubConfig.AuthCodeURL(oauthStateString)
69+
oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github"
70+
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
6371
c.Redirect(http.StatusTemporaryRedirect, url)
6472
case enum.Facebook.String():
73+
if oauth.OAuthProviders.FacebookConfig == nil {
74+
isProviderConfigured = false
75+
break
76+
}
6577
session.SetSocailLoginState(oauthStateString, enum.Facebook.String())
66-
oauth.OAuthProvider.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook"
67-
url := oauth.OAuthProvider.FacebookConfig.AuthCodeURL(oauthStateString)
78+
oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook"
79+
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
6880
c.Redirect(http.StatusTemporaryRedirect, url)
6981
default:
7082
c.JSON(422, gin.H{
7183
"message": "Invalid oauth provider",
7284
})
7385
}
86+
87+
if !isProviderConfigured {
88+
c.JSON(422, gin.H{
89+
"message": provider + " not configured",
90+
})
91+
}
7492
}
7593
}

server/oauth/oauth.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,57 @@
11
package oauth
22

33
import (
4+
"context"
5+
"log"
6+
47
"github.com/authorizerdev/authorizer/server/constants"
8+
"github.com/coreos/go-oidc/v3/oidc"
59
"golang.org/x/oauth2"
610
facebookOAuth2 "golang.org/x/oauth2/facebook"
711
githubOAuth2 "golang.org/x/oauth2/github"
8-
googleOAuth2 "golang.org/x/oauth2/google"
912
)
1013

11-
type OAuthProviders struct {
14+
type OAuthProvider struct {
1215
GoogleConfig *oauth2.Config
1316
GithubConfig *oauth2.Config
1417
FacebookConfig *oauth2.Config
1518
}
1619

17-
var OAuthProvider OAuthProviders
20+
type OIDCProvider struct {
21+
GoogleOIDC *oidc.Provider
22+
}
23+
24+
var (
25+
OAuthProviders OAuthProvider
26+
OIDCProviders OIDCProvider
27+
)
1828

1929
func InitOAuth() {
30+
ctx := context.Background()
2031
if constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "" {
21-
OAuthProvider.GoogleConfig = &oauth2.Config{
32+
p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
33+
if err != nil {
34+
log.Fatalln("error creating oidc provider for google:", err)
35+
}
36+
OIDCProviders.GoogleOIDC = p
37+
OAuthProviders.GoogleConfig = &oauth2.Config{
2238
ClientID: constants.GOOGLE_CLIENT_ID,
2339
ClientSecret: constants.GOOGLE_CLIENT_SECRET,
2440
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google",
25-
Endpoint: googleOAuth2.Endpoint,
26-
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
41+
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
42+
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
2743
}
2844
}
2945
if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" {
30-
OAuthProvider.GithubConfig = &oauth2.Config{
46+
OAuthProviders.GithubConfig = &oauth2.Config{
3147
ClientID: constants.GITHUB_CLIENT_ID,
3248
ClientSecret: constants.GITHUB_CLIENT_SECRET,
3349
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github",
3450
Endpoint: githubOAuth2.Endpoint,
3551
}
3652
}
3753
if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" {
38-
OAuthProvider.FacebookConfig = &oauth2.Config{
54+
OAuthProviders.FacebookConfig = &oauth2.Config{
3955
ClientID: constants.FACEBOOK_CLIENT_ID,
4056
ClientSecret: constants.FACEBOOK_CLIENT_SECRET,
4157
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook",

server/resolvers/token.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,16 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
8484
AccessToken: &token,
8585
AccessTokenExpiresAt: &expiresAt,
8686
User: &model.User{
87-
ID: userIdStr,
88-
Email: user.Email,
89-
Image: &user.Image,
90-
FirstName: &user.FirstName,
91-
LastName: &user.LastName,
92-
Roles: strings.Split(user.Roles, ","),
93-
CreatedAt: &user.CreatedAt,
94-
UpdatedAt: &user.UpdatedAt,
87+
ID: userIdStr,
88+
Email: user.Email,
89+
Image: &user.Image,
90+
FirstName: &user.FirstName,
91+
LastName: &user.LastName,
92+
Roles: strings.Split(user.Roles, ","),
93+
CreatedAt: &user.CreatedAt,
94+
UpdatedAt: &user.UpdatedAt,
95+
SignupMethod: user.SignupMethod,
96+
EmailVerifiedAt: &user.EmailVerifiedAt,
9597
},
9698
}
9799
return res, nil

0 commit comments

Comments
 (0)