Skip to content

Commit 935dae6

Browse files
feat(go-sdk): Automatically refresh expired authentication tokens (#1608)
The Looker Go SDK was not automatically refreshing expired authentication tokens, causing authentication to fail after one hour. This change modifies the AuthSession to proactively manage the token's lifecycle. An IsActive method was added to check the token's validity, and a Login method was added to refresh the token when needed. The Do method was updated to call Login before making any API requests, ensuring the token is always active. Fixes #1606 🦕 --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 9b596f3 commit 935dae6

File tree

1 file changed

+50
-2
lines changed

1 file changed

+50
-2
lines changed

go/rtl/auth.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/url"
1111
"os"
1212
"reflect"
13+
"sync"
1314
"time"
1415

1516
"golang.org/x/oauth2"
@@ -39,6 +40,47 @@ func (t *transportWithHeaders) RoundTrip(req *http.Request) (*http.Response, err
3940
type AuthSession struct {
4041
Config ApiSettings
4142
Client http.Client
43+
token *oauth2.Token
44+
source oauth2.TokenSource
45+
mu sync.RWMutex
46+
}
47+
48+
const tokenLeeway = 10 * time.Second
49+
50+
func (s *AuthSession) IsActive() bool {
51+
s.mu.RLock()
52+
defer s.mu.RUnlock()
53+
if s.token == nil {
54+
return false
55+
}
56+
return s.token.Expiry.After(time.Now().Add(tokenLeeway))
57+
}
58+
59+
func (s *AuthSession) Login() (*oauth2.Token, error) {
60+
// First, check with a read lock to avoid contention if the token is valid.
61+
s.mu.RLock()
62+
if s.token != nil && s.token.Expiry.After(time.Now().Add(tokenLeeway)) {
63+
token := s.token
64+
s.mu.RUnlock()
65+
return token, nil
66+
}
67+
s.mu.RUnlock()
68+
69+
// If the token is invalid or nil, acquire a write lock to refresh it.
70+
s.mu.Lock()
71+
defer s.mu.Unlock()
72+
73+
// Re-check after obtaining the write lock, in case another goroutine refreshed it.
74+
if s.token != nil && s.token.Expiry.After(time.Now().Add(tokenLeeway)) {
75+
return s.token, nil
76+
}
77+
78+
token, err := s.source.Token()
79+
if err != nil {
80+
return nil, err
81+
}
82+
s.token = token
83+
return token, nil
4284
}
4385

4486
func NewAuthSession(config ApiSettings) *AuthSession {
@@ -74,21 +116,27 @@ func NewAuthSessionWithTransport(config ApiSettings, transport http.RoundTripper
74116
&http.Client{Transport: appIdHeaderTransport},
75117
)
76118

119+
source := oauthConfig.TokenSource(ctx)
120+
77121
// Make use of oauth2 transport to handle token management
78122
oauthTransport := &oauth2.Transport{
79-
Source: oauthConfig.TokenSource(ctx),
123+
Source: source,
80124
// Will set "x-looker-appid" Header on all other requests
81125
Base: appIdHeaderTransport,
82126
}
83127

84128
return &AuthSession{
85129
Config: config,
86130
Client: http.Client{Transport: oauthTransport},
131+
source: source,
87132
}
88133
}
89134

90135
func (s *AuthSession) Do(result interface{}, method, ver, path string, reqPars map[string]interface{}, body interface{}, options *ApiSettings) error {
91-
136+
_, err := s.Login()
137+
if err != nil {
138+
return err
139+
}
92140
// prepare URL
93141
u := fmt.Sprintf("%s/api%s%s", s.Config.BaseUrl, ver, path)
94142

0 commit comments

Comments
 (0)