Skip to content

Commit

Permalink
Merge pull request #111 from tarent/google-oauth-userinfo
Browse files Browse the repository at this point in the history
changed to the google userinfo endpoint, because google+ shutdown
  • Loading branch information
smancke committed Jan 3, 2019
2 parents 2bb2308 + 368f2b3 commit 9c3bf63
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 57 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ loginsrv is a standalone minimalistic login server providing a [JWT](https://jwt
[![Coverage Status](https://coveralls.io/repos/github/tarent/loginsrv/badge.svg?branch=master)](https://coveralls.io/github/tarent/loginsrv?branch=master)
[![Join the chat at https://gitter.im/tarent/loginsrv](https://badges.gitter.im/tarent/loginsrv.svg)](https://gitter.im/tarent/loginsrv?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)


__** Attention: Update to v1.2.5 for Google Login Update !!!! **__

Google will stop support for the Google+ APIs. So we changed loginsrv to use the standard oauth endpoints for Google login.
Please update loginsrv to the master version or wait for release v1.2.5 if you are using google.

## Abstract

Loginsrv provides a minimal endpoint for authentication. The login is performed against the providers and returned as a JSON Web Token (JWT).
Expand Down Expand Up @@ -51,9 +57,9 @@ _Note for Caddy users_: Not all parameters are available in Caddy. See the table
| -cookie-expiry | string | session | X | Expiry duration for the cookie, e.g. 2h or 3h30m |
| -cookie-http-only | boolean | true | X | Set the cookie with the HTTP only flag |
| -cookie-name | string | "jwt_token" | X | Name of the JWT cookie |
| -github | value | | X | OAuth config in the form: client_id=..,client_secret=..[,scope=..,][redirect_uri=..] |
| -google | value | | X | OAuth config in the form: client_id=..,client_secret=..,scope=..[redirect_uri=..] |
| -bitbucket | value | | X | OAuth config in the form: client_id=..,client_secret=..,[,scope=..][redirect_uri=..] |
| -github | value | | X | OAuth config in the form: client_id=..,client_secret=..[,scope=..][,redirect_uri=..] |
| -google | value | | X | OAuth config in the form: client_id=..,client_secret=..[,scope=..][,redirect_uri=..] |
| -bitbucket | value | | X | OAuth config in the form: client_id=..,client_secret=..[,scope=..][,redirect_uri=..] |
| -facebook | value | | X | OAuth config in the form: client_id=..,client_secret=..,scope=email..[redirect_uri=..] |
| -gitlab | value | | X | OAuth config in the form: client_id=..,client_secret=..[,scope=..,][redirect_uri=..]
| -host | string | "localhost" | - | Host to listen on |
Expand Down Expand Up @@ -318,10 +324,6 @@ if loginsrv is routed through a reverse proxy, if the headers `X-Forwarded-Host`
$ docker run -p 80:80 tarent/loginsrv -github client_id=xxx,client_secret=yyy
```

### Note for Google's OAuth 2
You can use `scope=https://www.googleapis.com/auth/userinfo.email` [Google Plus API](https://console.cloud.google.com/apis/library/plus.googleapis.com/). When configuring OAuth 2 credentials in Google Cloud Console, don't forget to enable corresponding API's.
For example, for `scope=https://www.googleapis.com/auth/userinfo.profile` [Google People API](https://console.cloud.google.com/apis/library/people.googleapis.com/) must be enabled for your project. Keep in mind that it usually takes a few minutes for this setting to take effect.

### Note for Facebook's OAuth 2
Make sure you ask for the scope `email` when adding your Facebook config option. Otherwise the provider won't be able to fetch the user's email.

Expand Down
42 changes: 21 additions & 21 deletions oauth2/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,34 @@ import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"

"github.com/tarent/loginsrv/model"
)

var googleAPI = "https://www.googleapis.com/plus/v1"

var googleUserinfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"

func init() {
RegisterProvider(providerGoogle)
}

type GoogleUser struct {
DisplayName string
Emails []struct {
Value string
}
Image struct {
Url string
}
Domain string
Name string `json:"name"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
Picture string `json:"picture"`
HostedGsuiteDomain string `json:"hd"`
}

var providerGoogle = Provider{
Name: "google",
AuthURL: "https://accounts.google.com/o/oauth2/v2/auth",
TokenURL: "https://www.googleapis.com/oauth2/v4/token",
Name: "google",
AuthURL: "https://accounts.google.com/o/oauth2/v2/auth",
TokenURL: "https://www.googleapis.com/oauth2/v4/token",
DefaultScopes: "email profile",
GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) {
gu := GoogleUser{}
url := fmt.Sprintf("%v/people/me?alt=json&access_token=%v", googleAPI, token.AccessToken)
url := fmt.Sprintf("%v?access_token=%v", googleUserinfoEndpoint, token.AccessToken)
resp, err := http.Get(url)

if err != nil {
Expand All @@ -60,19 +58,21 @@ var providerGoogle = Provider{
return model.UserInfo{}, "", fmt.Errorf("error parsing google get user info: %v", err)
}

if len(gu.Emails) == 0 {
if len(gu.Email) == 0 {
return model.UserInfo{}, "", fmt.Errorf("invalid google response: no email address returned.")
}

reg := regexp.MustCompile(`\?.*$`)
if !gu.EmailVerified {
return model.UserInfo{}, "", fmt.Errorf("invalid google response: users email address not verified by google.")
}

return model.UserInfo{
Sub: gu.Emails[0].Value,
Picture: reg.ReplaceAllString(gu.Image.Url, "${1}"),
Name: gu.DisplayName,
Email: gu.Emails[0].Value,
Sub: gu.Email,
Picture: gu.Picture,
Name: gu.Name,
Email: gu.Email,
Origin: "google",
Domain: gu.Domain,
Domain: gu.HostedGsuiteDomain,
}, string(b), nil
},
}
44 changes: 15 additions & 29 deletions oauth2/google_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,17 @@ import (
)

var googleTestUserResponse = `{
"kind": "plus#person",
"etag": "\"XX\"",
"sub": "10467329456789",
"name": "Testy Test",
"given_name": "Testy",
"family_name": "Test",
"profile": "https://plus.google.com/10467329456789",
"picture": "https://lh6.googleusercontent.com/-alknmlknzT_YQ/AAAAAAAAAAI/AAAAAAAAABU/4gNvDUeED14/photo.jpg",
"email": "test@example.com",
"email_verified": true,
"gender": "male",
"emails": [
{
"value": "test@gmail.com",
"type": "account"
}
],
"objectType": "person",
"id": "1",
"displayName": "Testy Test",
"name": {
"familyName": "Test",
"givenName": "Testy"
},
"url": "https://plus.google.com/X",
"image": {
"url": "https://lh3.googleusercontent.com/X/X/X/X/photo.jpg?sz=50",
"isDefault": true
},
"isPlusUser": true,
"circledByCount": 0,
"verified": false,
"domain": "gmail.com"
"locale": "de",
"hd": "example.com"
}`

func Test_Google_getUserInfo(t *testing.T) {
Expand All @@ -44,14 +30,14 @@ func Test_Google_getUserInfo(t *testing.T) {
}))
defer server.Close()

googleAPI = server.URL
googleUserinfoEndpoint = server.URL

u, rawJSON, err := providerGoogle.GetUserInfo(TokenInfo{AccessToken: "secret"})
NoError(t, err)
Equal(t, "test@gmail.com", u.Sub)
Equal(t, "test@gmail.com", u.Email)
Equal(t, "https://lh3.googleusercontent.com/X/X/X/X/photo.jpg", u.Picture)
Equal(t, "test@example.com", u.Sub)
Equal(t, "test@example.com", u.Email)
Equal(t, "https://lh6.googleusercontent.com/-alknmlknzT_YQ/AAAAAAAAAAI/AAAAAAAAABU/4gNvDUeED14/photo.jpg", u.Picture)
Equal(t, "Testy Test", u.Name)
Equal(t, "gmail.com", u.Domain)
Equal(t, "example.com", u.Domain)
Equal(t, googleTestUserResponse, rawJSON)
}
2 changes: 2 additions & 0 deletions oauth2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ func (manager *Manager) AddConfig(providerName string, opts map[string]string) e

if scope, exist := opts["scope"]; exist {
cfg.Scope = scope
} else {
cfg.Scope = p.DefaultScopes
}

if redirectURI, exist := opts["redirect_uri"]; exist {
Expand Down
4 changes: 4 additions & 0 deletions oauth2/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ type Provider struct {
// The url for token exchange
TokenURL string

// Default Scopes is a space separated list of oauth scopes to use for this provider.
// This list can be overwritten by configuration.
DefaultScopes string

// GetUserInfo is a provider specific Implementation
// for fetching the user information.
// Possible keys in the returned map are:
Expand Down

0 comments on commit 9c3bf63

Please sign in to comment.