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

Auto refresh auth token #88

Merged
merged 16 commits into from
May 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Usage of google_auth_proxy:
-cookie-expire=168h0m0s: expire timeframe for cookie
-cookie-httponly=true: set HttpOnly cookie flag
-cookie-https-only=true: set secure (HTTPS) cookies (deprecated. use --cookie-secure setting)
-cookie-refresh=0: refresh the cookie when less than this much time remains before expiration; 0 to disable
-cookie-secret="": the seed string for secure cookies
-cookie-secure=true: set secure (HTTPS) cookie flag
-custom-templates-dir="": path to custom html templates
Expand All @@ -96,6 +97,7 @@ Usage of google_auth_proxy:
-scope="": Oauth scope specification
-skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
-upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path
-validate-url="": Access token validation endpoint
-version=false: print version string
```

Expand Down
9 changes: 7 additions & 2 deletions contrib/google_auth_proxy.cfg.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@


## Cookie Settings
## Secret - the seed string for secure cookies
## Secret - the seed string for secure cookies; should be 16, 24, or 32 bytes
## for use with an AES cipher when cookie_refresh or pass_access_code
## is set
## Domain - optional cookie domain to force cookies to (ie: .yourcompany.com)
## Expire - expire timeframe for cookie
## Refresh - refresh the cookie when less than this much time remains before
## expiration; should be less than cookie_expire; set to 0 to disable
# cookie_secret = ""
# cookie_domain = ""
# cookie_expire = "168h"
# cookie_refresh = "144h"
# cookie_secure = true
# cookie_httponly = true

# pass_access_code = true
8 changes: 4 additions & 4 deletions cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import (
"time"
)

func validateCookie(cookie *http.Cookie, seed string) (string, bool) {
func validateCookie(cookie *http.Cookie, seed string) (string, time.Time, bool) {
// value, timestamp, sig
parts := strings.Split(cookie.Value, "|")
if len(parts) != 3 {
return "", false
return "", time.Unix(0, 0), false
}
sig := cookieSignature(seed, cookie.Name, parts[0], parts[1])
if checkHmac(parts[2], sig) {
Expand All @@ -28,11 +28,11 @@ func validateCookie(cookie *http.Cookie, seed string) (string, bool) {
// it's a valid cookie. now get the contents
rawValue, err := base64.URLEncoding.DecodeString(parts[0])
if err == nil {
return string(rawValue), true
return string(rawValue), time.Unix(int64(ts), 0), true
}
}
}
return "", false
return "", time.Unix(0, 0), false
}

func signedCookieValue(seed string, key string, value string) string {
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func main() {
flagSet.String("cookie-secret", "", "the seed string for secure cookies")
flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
flagSet.Duration("cookie-refresh", time.Duration(0)*time.Hour, "refresh the cookie when less than this much time remains before expiration; 0 to disable")
flagSet.Bool("cookie-https-only", true, "set secure (HTTPS) cookies (deprecated. use --cookie-secure setting)")
flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag")
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
Expand All @@ -55,6 +56,7 @@ func main() {
flagSet.String("login-url", "", "Authentication endpoint")
flagSet.String("redeem-url", "", "Token redemption endpoint")
flagSet.String("profile-url", "", "Profile access endpoint")
flagSet.String("validate-url", "", "Access token validation endpoint")
flagSet.String("scope", "", "Oauth scope specification")

flagSet.Parse(os.Args[1:])
Expand Down
103 changes: 67 additions & 36 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ type OauthProxy struct {
CookieSecure bool
CookieHttpOnly bool
CookieExpire time.Duration
CookieRefresh time.Duration
Validator func(string) bool

redirectUrl *url.URL // the url to receive requests at
provider providers.Provider
oauthRedemptionUrl *url.URL // endpoint to redeem the code
oauthLoginUrl *url.URL // to redirect the user to
oauthValidateUrl *url.URL // to validate the access token
oauthScope string
clientID string
clientSecret string
Expand All @@ -47,6 +49,7 @@ type OauthProxy struct {
DisplayHtpasswdForm bool
serveMux http.Handler
PassBasicAuth bool
PassAccessToken bool
AesCipher cipher.Block
skipAuthRegex []string
compiledRegex []*regexp.Regexp
Expand Down Expand Up @@ -120,12 +123,12 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
log.Printf("Cookie settings: secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain)

var aes_cipher cipher.Block
if opts.PassAccessToken {
if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
var err error
aes_cipher, err = aes.NewCipher([]byte(opts.CookieSecret))
if err != nil {
log.Fatal("error creating AES cipher with "+
"pass_access_token == true: %s", err)
"cookie-secret ", opts.CookieSecret, ": ", err)
}
}

Expand All @@ -136,6 +139,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
CookieSecure: opts.CookieSecure,
CookieHttpOnly: opts.CookieHttpOnly,
CookieExpire: opts.CookieExpire,
CookieRefresh: opts.CookieRefresh,
Validator: validator,

clientID: opts.ClientID,
Expand All @@ -144,11 +148,13 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
provider: opts.provider,
oauthRedemptionUrl: opts.provider.Data().RedeemUrl,
oauthLoginUrl: opts.provider.Data().LoginUrl,
oauthValidateUrl: opts.provider.Data().ValidateUrl,
serveMux: serveMux,
redirectUrl: redirectUrl,
skipAuthRegex: opts.SkipAuthRegex,
compiledRegex: opts.CompiledRegex,
PassBasicAuth: opts.PassBasicAuth,
PassAccessToken: opts.PassAccessToken,
AesCipher: aes_cipher,
templates: loadTemplates(opts.CustomTemplatesDir),
}
Expand Down Expand Up @@ -223,7 +229,7 @@ func (p *OauthProxy) redeemCode(host, code string) (string, string, error) {
return access_token, email, nil
}

func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
func (p *OauthProxy) MakeCookie(req *http.Request, value string, expiration time.Duration) *http.Cookie {
domain := req.Host
if h, _, err := net.SplitHostPort(domain); err == nil {
domain = h
Expand All @@ -234,40 +240,76 @@ func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
}
domain = p.CookieDomain
}
cookie := &http.Cookie{

if value != "" {
value = signedCookieValue(p.CookieSeed, p.CookieKey, value)
}

return &http.Cookie{
Name: p.CookieKey,
Value: "",
Value: value,
Path: "/",
Domain: domain,
HttpOnly: p.CookieHttpOnly,
Secure: p.CookieSecure,
Expires: time.Now().Add(time.Duration(1) * time.Hour * -1),
Expires: time.Now().Add(expiration),
}
http.SetCookie(rw, cookie)
}

func (p *OauthProxy) ClearCookie(rw http.ResponseWriter, req *http.Request) {
http.SetCookie(rw, p.MakeCookie(req, "", time.Duration(1)*time.Hour*-1))
}

func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val string) {
http.SetCookie(rw, p.MakeCookie(req, val, p.CookieExpire))
}

domain := req.Host
if h, _, err := net.SplitHostPort(domain); err == nil {
domain = h
func (p *OauthProxy) ValidateToken(access_token string) bool {
if access_token == "" || p.oauthValidateUrl == nil {
return false
}
if p.CookieDomain != "" {
if !strings.HasSuffix(domain, p.CookieDomain) {
log.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)

req, err := http.NewRequest("GET",
p.oauthValidateUrl.String()+"?access_token="+access_token, nil)
if err != nil {
log.Printf("failed building token validation request: %s", err)
return false
}

httpclient := &http.Client{}
resp, err := httpclient.Do(req)
if err != nil {
log.Printf("token validation request failed: %s", err)
return false
}
return resp.StatusCode == 200
}

func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) {
var value string
var timestamp time.Time
cookie, err := req.Cookie(p.CookieKey)
if err == nil {
value, timestamp, ok = validateCookie(cookie, p.CookieSeed)
if ok {
email, user, access_token, err = parseCookieValue(
value, p.AesCipher)
}
domain = p.CookieDomain
}
cookie := &http.Cookie{
Name: p.CookieKey,
Value: signedCookieValue(p.CookieSeed, p.CookieKey, val),
Path: "/",
Domain: domain,
HttpOnly: p.CookieHttpOnly,
Secure: p.CookieSecure,
Expires: time.Now().Add(p.CookieExpire),
if err != nil {
log.Printf(err.Error())
ok = false
} else if p.CookieRefresh != time.Duration(0) {
expires := timestamp.Add(p.CookieExpire)
refresh_threshold := time.Now().Add(p.CookieRefresh)
if refresh_threshold.Unix() > expires.Unix() {
ok = p.Validator(email) && p.ValidateToken(access_token)
if ok {
p.SetCookie(rw, req, value)
}
}
}
http.SetCookie(rw, cookie)
return
}

func (p *OauthProxy) PingPage(rw http.ResponseWriter) {
Expand Down Expand Up @@ -440,18 +482,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}

if !ok {
cookie, err := req.Cookie(p.CookieKey)
if err == nil {
var value string
value, ok = validateCookie(cookie, p.CookieSeed)
if ok {
email, user, access_token, err = parseCookieValue(
value, p.AesCipher)
if err != nil {
log.Printf(err.Error())
}
}
}
email, user, access_token, ok = p.ProcessCookie(rw, req)
}

if !ok {
Expand All @@ -469,7 +500,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
req.Header["X-Forwarded-User"] = []string{user}
req.Header["X-Forwarded-Email"] = []string{email}
}
if access_token != "" {
if p.PassAccessToken {
req.Header["X-Forwarded-Access-Token"] = []string{access_token}
}
if email == "" {
Expand Down
Loading