Skip to content
This repository was archived by the owner on Jan 24, 2019. It is now read-only.

Commit b9ae5dc

Browse files
committed
Merge pull request #116 from jehiah/google_refresh_token_116
Google - use offline access token
2 parents ae2a1e5 + aa0a725 commit b9ae5dc

File tree

8 files changed

+124
-17
lines changed

8 files changed

+124
-17
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ For Google, the registration steps are:
5050
* Fill in the necessary fields and Save (this is _required_)
5151
5. Take note of the **Client ID** and **Client Secret**
5252

53+
It's recommended to refresh sessions on a short interval (1h) with `cookie-refresh` setting which validates that the account is still authorized.
54+
5355
### GitHub Auth Provider
5456

5557
1. Create a new project: https://github.com/settings/developers
@@ -100,7 +102,7 @@ Usage of oauth2_proxy:
100102
-cookie-expire=168h0m0s: expire timeframe for cookie
101103
-cookie-httponly=true: set HttpOnly cookie flag
102104
-cookie-key="_oauth2_proxy": the name of the cookie that the oauth_proxy creates
103-
-cookie-refresh=0: refresh the cookie when less than this much time remains before expiration; 0 to disable
105+
-cookie-refresh=0: refresh the cookie after this duration; 0 to disable
104106
-cookie-secret="": the seed string for secure cookies
105107
-cookie-secure=true: set secure (HTTPS) cookie flag
106108
-custom-templates-dir="": path to custom html templates

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func main() {
5050
flagSet.String("cookie-secret", "", "the seed string for secure cookies")
5151
flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
5252
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
53-
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie when less than this much time remains before expiration; 0 to disable")
53+
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
5454
flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
5555
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
5656

oauthproxy.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,13 @@ func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (e
246246
} else if ok && p.CookieRefresh != time.Duration(0) {
247247
refresh := timestamp.Add(p.CookieRefresh)
248248
if refresh.Before(time.Now()) {
249-
ok = p.Validator(email) && p.provider.ValidateToken(access_token)
249+
log.Printf("refreshing %s old session for %s (refresh after %s)", time.Now().Sub(timestamp), email, p.CookieRefresh)
250+
ok = p.Validator(email)
251+
log.Printf("re-validating %s valid:%v", email, ok)
252+
if ok {
253+
ok = p.provider.ValidateToken(access_token)
254+
log.Printf("re-validating access token. valid:%v", ok)
255+
}
250256
if ok {
251257
p.SetCookie(rw, req, value)
252258
}
@@ -432,6 +438,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
432438
http.Redirect(rw, req, redirect, 302)
433439
return
434440
} else {
441+
log.Printf("validating: %s is unauthorized")
435442
p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
436443
return
437444
}

options_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ func TestDefaultProviderApiSettings(t *testing.T) {
9696
o := testOptions()
9797
assert.Equal(t, nil, o.Validate())
9898
p := o.provider.Data()
99-
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth",
99+
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
100100
p.LoginUrl.String())
101-
assert.Equal(t, "https://accounts.google.com/o/oauth2/token",
101+
assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
102102
p.RedeemUrl.String())
103103
assert.Equal(t, "", p.ProfileUrl.String())
104104
assert.Equal(t, "profile email", p.Scope)

providers/google.go

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
package providers
22

33
import (
4+
"bytes"
45
"encoding/base64"
56
"encoding/json"
67
"errors"
8+
"fmt"
9+
"io/ioutil"
10+
"net/http"
711
"net/url"
812
"strings"
913
)
1014

1115
type GoogleProvider struct {
1216
*ProviderData
17+
RedeemRefreshUrl *url.URL
1318
}
1419

1520
func NewGoogleProvider(p *ProviderData) *GoogleProvider {
1621
p.ProviderName = "Google"
1722
if p.LoginUrl.String() == "" {
1823
p.LoginUrl = &url.URL{Scheme: "https",
1924
Host: "accounts.google.com",
20-
Path: "/o/oauth2/auth"}
25+
Path: "/o/oauth2/auth",
26+
// to get a refresh token. see https://developers.google.com/identity/protocols/OAuth2WebServer#offline
27+
RawQuery: "access_type=offline",
28+
}
2129
}
2230
if p.RedeemUrl.String() == "" {
2331
p.RedeemUrl = &url.URL{Scheme: "https",
24-
Host: "accounts.google.com",
25-
Path: "/o/oauth2/token"}
32+
Host: "www.googleapis.com",
33+
Path: "/oauth2/v3/token"}
2634
}
2735
if p.ValidateUrl.String() == "" {
2836
p.ValidateUrl = &url.URL{Scheme: "https",
@@ -76,3 +84,90 @@ func jwtDecodeSegment(seg string) ([]byte, error) {
7684
func (p *GoogleProvider) ValidateToken(access_token string) bool {
7785
return validateToken(p, access_token, nil)
7886
}
87+
88+
func (p *GoogleProvider) Redeem(redirectUrl, code string) (body []byte, token string, err error) {
89+
if code == "" {
90+
err = errors.New("missing code")
91+
return
92+
}
93+
94+
params := url.Values{}
95+
params.Add("redirect_uri", redirectUrl)
96+
params.Add("client_id", p.ClientID)
97+
params.Add("client_secret", p.ClientSecret)
98+
params.Add("code", code)
99+
params.Add("grant_type", "authorization_code")
100+
var req *http.Request
101+
req, err = http.NewRequest("POST", p.RedeemUrl.String(), bytes.NewBufferString(params.Encode()))
102+
if err != nil {
103+
return
104+
}
105+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
106+
107+
resp, err := http.DefaultClient.Do(req)
108+
if err != nil {
109+
return
110+
}
111+
body, err = ioutil.ReadAll(resp.Body)
112+
resp.Body.Close()
113+
if err != nil {
114+
return
115+
}
116+
117+
if resp.StatusCode != 200 {
118+
err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemUrl.String(), body)
119+
return
120+
}
121+
122+
var jsonResponse struct {
123+
AccessToken string `json:"access_token"`
124+
RefreshToken string `json:"refresh_token"`
125+
}
126+
err = json.Unmarshal(body, &jsonResponse)
127+
if err != nil {
128+
return
129+
}
130+
131+
token, err = p.redeemRefreshToken(jsonResponse.RefreshToken)
132+
return
133+
}
134+
135+
func (p *GoogleProvider) redeemRefreshToken(refreshToken string) (token string, err error) {
136+
// https://developers.google.com/identity/protocols/OAuth2WebServer#refresh
137+
params := url.Values{}
138+
params.Add("client_id", p.ClientID)
139+
params.Add("client_secret", p.ClientSecret)
140+
params.Add("refresh_token", refreshToken)
141+
params.Add("grant_type", "refresh_token")
142+
var req *http.Request
143+
req, err = http.NewRequest("POST", p.RedeemUrl.String(), bytes.NewBufferString(params.Encode()))
144+
if err != nil {
145+
return
146+
}
147+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
148+
149+
resp, err := http.DefaultClient.Do(req)
150+
if err != nil {
151+
return
152+
}
153+
var body []byte
154+
body, err = ioutil.ReadAll(resp.Body)
155+
resp.Body.Close()
156+
if err != nil {
157+
return
158+
}
159+
160+
if resp.StatusCode != 200 {
161+
err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemUrl.String(), body)
162+
return
163+
}
164+
165+
var jsonResponse struct {
166+
AccessToken string `json:"access_token"`
167+
}
168+
err = json.Unmarshal(body, &jsonResponse)
169+
if err != nil {
170+
return
171+
}
172+
return jsonResponse.AccessToken, nil
173+
}

providers/google_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ func TestGoogleProviderDefaults(t *testing.T) {
2323
p := newGoogleProvider()
2424
assert.NotEqual(t, nil, p)
2525
assert.Equal(t, "Google", p.Data().ProviderName)
26-
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth",
26+
assert.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
2727
p.Data().LoginUrl.String())
28-
assert.Equal(t, "https://accounts.google.com/o/oauth2/token",
28+
assert.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
2929
p.Data().RedeemUrl.String())
3030
assert.Equal(t, "https://www.googleapis.com/oauth2/v1/tokeninfo",
3131
p.Data().ValidateUrl.String())

providers/provider_default.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,19 @@ func (p *ProviderData) Redeem(redirectUrl, code string) (body []byte, token stri
5656
return body, v.Get("access_token"), err
5757
}
5858

59+
// GetLoginURL with typical oauth parameters
5960
func (p *ProviderData) GetLoginURL(redirectURI, finalRedirect string) string {
60-
params := url.Values{}
61-
params.Add("redirect_uri", redirectURI)
62-
params.Add("approval_prompt", "force")
61+
var a url.URL
62+
a = *p.LoginUrl
63+
params, _ := url.ParseQuery(a.RawQuery)
64+
params.Set("redirect_uri", redirectURI)
65+
params.Set("approval_prompt", "force")
6366
params.Add("scope", p.Scope)
64-
params.Add("client_id", p.ClientID)
65-
params.Add("response_type", "code")
67+
params.Set("client_id", p.ClientID)
68+
params.Set("response_type", "code")
6669
if strings.HasPrefix(finalRedirect, "/") {
6770
params.Add("state", finalRedirect)
6871
}
69-
return fmt.Sprintf("%s?%s", p.LoginUrl, params.Encode())
72+
a.RawQuery = params.Encode()
73+
return a.String()
7074
}

validator.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ func newValidatorImpl(domains []string, usersFile string,
8383
if allowAll {
8484
valid = true
8585
}
86-
log.Printf("validating: is %s valid? %v", email, valid)
8786
return valid
8887
}
8988
return validator

0 commit comments

Comments
 (0)