@@ -38,7 +38,61 @@ func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err er
3838 if err != nil {
3939 return nil, fmt.Errorf("token exchange: %v", err)
4040 }
41+ s, err = p.createSessionState(ctx, token)
42+ if err != nil {
43+ return nil, fmt.Errorf("unable to update session: %v", err)
44+ }
45+ return
46+}
47+
48+// RefreshSessionIfNeeded checks if the session has expired and uses the
49+// RefreshToken to fetch a new ID token if required
50+func (p *OIDCProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
51+ if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
52+ return false, nil
53+ }
54+
55+ origExpiration := s.ExpiresOn
56+
57+ err := p.redeemRefreshToken(s)
58+ if err != nil {
59+ return false, fmt.Errorf("unable to redeem refresh token: %v", err)
60+ }
61+
62+ fmt.Printf("refreshed id token %s (expired on %s)\n", s, origExpiration)
63+ return true, nil
64+}
4165
66+func (p *OIDCProvider) redeemRefreshToken(s *SessionState) (err error) {
67+ c := oauth2.Config{
68+ ClientID: p.ClientID,
69+ ClientSecret: p.ClientSecret,
70+ Endpoint: oauth2.Endpoint{
71+ TokenURL: p.RedeemURL.String(),
72+ },
73+ }
74+ ctx := context.Background()
75+ t := &oauth2.Token{
76+ RefreshToken: s.RefreshToken,
77+ Expiry: time.Now().Add(-time.Hour),
78+ }
79+ token, err := c.TokenSource(ctx, t).Token()
80+ if err != nil {
81+ return fmt.Errorf("failed to get token: %v", err)
82+ }
83+ newSession, err := p.createSessionState(ctx, token)
84+ if err != nil {
85+ return fmt.Errorf("unable to update session: %v", err)
86+ }
87+ s.AccessToken = newSession.AccessToken
88+ s.IDToken = newSession.IDToken
89+ s.RefreshToken = newSession.RefreshToken
90+ s.ExpiresOn = newSession.ExpiresOn
91+ s.Email = newSession.Email
92+ return
93+}
94+
95+func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Token) (*SessionState, error) {
4296 rawIDToken, ok := token.Extra("id_token").(string)
4397 if !ok {
4498 return nil, fmt.Errorf("token response did not contain an id_token")
@@ -66,29 +120,11 @@ func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err er
66120 return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
67121 }
68122
69− s = &SessionState{
123+ return &SessionState{
70124 AccessToken: token.AccessToken,
71125 IDToken: rawIDToken,
72126 RefreshToken: token.RefreshToken,
73127 ExpiresOn: token.Expiry,
74128 Email: claims.Email,
75− }
76−
77− return
78−}
79−
80−// RefreshSessionIfNeeded checks if the session has expired and uses the
81−// RefreshToken to fetch a new ID token if required
82−//
83−// WARNGING: This implementation is broken and does not check with the upstream
84−// OIDC provider before refreshing the session
85−func (p *OIDCProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
86− if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
87− return false, nil
88− }
89−
90− origExpiration := s.ExpiresOn
91− s.ExpiresOn = time.Now().Add(time.Second).Truncate(time.Second)
92− fmt.Printf("refreshed access token %s (expired on %s)\n", s, origExpiration)
93− return false, nil
129+ }, nil
94130}