Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement refreshing within OIDC provider #5

Merged
merged 3 commits into from
Nov 26, 2018
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many ma
-cookie-secure=false
-email-domain example.com

If you enable cookie-refresh, it should be set to the same duration as token lifetime
(due to a limitation in `oauth2_proxy` - see [bitly/oauth2_proxy#620](https://github.com/bitly/oauth2_proxy/pull/620)).


## Email Authentication

To authorize by email domain use `--email-domain=yourcompany.com`. To authorize individual email addresses use `--authenticated-emails-file=/path/to/file` with one email per line. To authorize all email addresses use `--email-domain=*`.
Expand Down
9 changes: 5 additions & 4 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,6 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
if value != "" {
value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
if len(value) > 4096 {
// Cookies cannot be larger than 4kb
log.Printf("WARNING - Cookie Size: %d bytes", len(value))
}
}
return p.makeCookie(req, p.CookieName, value, expiration, now)
}
Expand All @@ -281,6 +277,11 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
log.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
}
}
if len(value) > 3600 {
// nginx default response header limit is 4KiB, other software may have similar limits
// threshold includes margin for header name, cookie name, other cookie options
log.Printf("WARNING - %s cookie is very big: %d bytes", name, len(value))
}

return &http.Cookie{
Name: name,
Expand Down
68 changes: 53 additions & 15 deletions providers/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,58 @@ func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err er
if err != nil {
return nil, fmt.Errorf("token exchange: %v", err)
}
s, err = p.createSessionState(token, ctx)
if err != nil {
return nil, fmt.Errorf("unable to update session: %v", err)
}
return
}

func (p *OIDCProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
return false, nil
}

origExpiration := s.ExpiresOn

err := p.redeemRefreshToken(s)
if err != nil {
return false, fmt.Errorf("unable to redeem refresh token: %v", err)
}

fmt.Printf("refreshed id token %s (expired on %s)\n", s, origExpiration)
return true, nil
}

func (p *OIDCProvider) redeemRefreshToken(s *SessionState) (err error) {
c := oauth2.Config{
ClientID: p.ClientID,
ClientSecret: p.ClientSecret,
Endpoint: oauth2.Endpoint{
TokenURL: p.RedeemURL.String(),
},
}
ctx := context.Background()
t := &oauth2.Token{
RefreshToken: s.RefreshToken,
Expiry: time.Now().Add(-time.Hour),
}
token, err := c.TokenSource(ctx, t).Token()
if err != nil {
return fmt.Errorf("failed to get token: %v", err)
}
newSession, err := p.createSessionState(token, ctx)
if err != nil {
return fmt.Errorf("unable to update session: %v", err)
}
s.AccessToken = newSession.AccessToken
s.RefreshToken = newSession.RefreshToken
s.ExpiresOn = newSession.ExpiresOn
s.Email = newSession.Email
return
}

func (p *OIDCProvider) createSessionState(token *oauth2.Token, ctx context.Context) (*SessionState, error) {
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, fmt.Errorf("token response did not contain an id_token")
Expand Down Expand Up @@ -63,23 +114,10 @@ func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err er
return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
}

s = &SessionState{
return &SessionState{
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
ExpiresOn: token.Expiry,
Email: claims.Email,
}

return
}

func (p *OIDCProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
return false, nil
}

origExpiration := s.ExpiresOn
s.ExpiresOn = time.Now().Add(time.Second).Truncate(time.Second)
fmt.Printf("refreshed access token %s (expired on %s)\n", s, origExpiration)
return false, nil
}, nil
}