Token storage for https://godoc.org/golang.org/x/oauth2

1,408 views
Skip to first unread message

Aaron Torres

unread,
Feb 24, 2015, 4:56:56 PM2/24/15
to golan...@googlegroups.com
I'm having difficulty on how to introduce custom storage for tokens using https://godoc.org/golang.org/x/oauth2. Currently, I can store the token after Exchange(), but in the event of an automatic refresh, I have no hooks to capture that the refresh occurred. I'd have to check after every RoundTrip to see if it changed. In the old system, I was using token cache for just this purpose. 

I keep thinking that a TokenSource is the answer. I could create a custom TokenSource that updates the long-term storage after doing the exchange. However, when I look into this all the functionality for doing a refresh is in tokenRefresher and it's private all the way down. I keep thinking there's a different hook somewhere to accomplish what I want.

Am I missing something? This is what an implementation would look like with my current thinking if either tokenRefresher or retrieveToken were public.

type CustomToken struct{
  ctx
Context
 
*TokenRefresher //currently private
  db  
*CustomStorage
}


func
(t *CustomToken) Token() (*oauth2.Token, error){
  token
, err := t.TokenRefresher.Token()
 
if err != nil{
   
return nil, err
 
}


  err
= t.db.store(token)
 
if err != nil{
   
return nil, err
 
}
 
 
return token, nil
}


OR

type CustomToken struct{
  ctx          
Context
  conf        
*Config
  refreshToken
string
  db
*CustomStorage
}


func
(t *CustomToken) Token() (*oauth2.Token, error){
 
if tf.refreshToken == "" {
   
return nil, errors.New("oauth2: token expired and refresh token is not set")
 
}


 
// currently private
  tk
, err := RetrieveToken(tf.ctx, tf.conf, url.Values{
   
"grant_type":    {"refresh_token"},
   
"refresh_token": {tf.refreshToken},
 
})


 
if err != nil {
   
return nil, err
 
}
 
if tf.refreshToken != tk.RefreshToken {
    tf
.refreshToken = tk.RefreshToken
 
}
  err
= t.db.store(token)
 
if err != nil{
   
return nil, err
 
}
 
return tk, err
}



John Leach

unread,
Aug 2, 2015, 3:24:28 PM8/2/15
to golang-nuts
Hi Aaron,

did you get anywhere with something like this? I'm in the same situation and want to cache the token to disk in between executions of a cli tool.

I've even tried just manually getting and storing the token before exiting but I can't even figure out how to access it!

I thought I'd be able to access it from the oauth2.Transport from the http client, but the TokenSource is somehow not available to me, despite it apparently being exposed publicly here:

https://github.com/golang/oauth2/blob/master/transport.go#L23

Though I'm a go newbie so maybe I'm just missing something obvious there.

Thanks,

John.

Aaron Torres

unread,
Aug 2, 2015, 11:31:59 PM8/2/15
to golang-nuts
Hey John, I did find a solution that worked pretty nicely for me. I'll try to summarize. The general idea is to create a new Client that looks something like this https://gist.github.com/agtorre/350c5b4ce0ccebc5ac0f 

Whenever the Client or TokenSource does an exchange it automatically stores the token in Redis (or a db, file, or whatever). As a result of the Reuse token source, whenever a token is expired it'll automatically fetch and store a new one. You can pass around the client, and generate tokensources on demand or call exchange directly.

This doesn't really deal with re-retrieval of the token from Redis, but there are a bunch of ways to approach this once its stored.

Hopefully this helps.

patrick...@gmail.com

unread,
Dec 26, 2015, 1:38:03 PM12/26/15
to golang-nuts
Hi Aaron, thanks for the gist you provided :)

I'm struggling to understand how to use it, image if I have this: 

var fitbitConf = &oauth2.Config{
ClientID:     data.FitbitClientID,
ClientSecret: data.FitbitClientSecret,
Scopes:       []string{"activity", "weight", "profile"},
Endpoint: oauth2.Endpoint{
AuthURL:  "https://www.fitbit.com/oauth2/authorize",
TokenURL: "https://api.fitbit.com/oauth2/token",
},
}

how would create (and use) a new client based on this configuration?

Aaron Torres

unread,
Dec 26, 2015, 5:40:06 PM12/26/15
to patrick...@gmail.com, golang-nuts
Hey Patrick, looking back at the gist I realized I had written it a bit confusing. I just added some modifications to it and added a usage example as a comment. Hopefully this makes it a bit more clear! For your example, the Config you pass in will be fitbitConf.



--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/5jFjsIg_d4M/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Patrick Guido

unread,
Dec 26, 2015, 6:19:48 PM12/26/15
to Aaron Torres, golang-nuts
Thank you very much! One last question (sorry!), how do I create a new config/client from a stored token?

I'm now successfully storing the token on datastore (here's my gist https://gist.github.com/patrick91/f1d725a985b552261448 )

But I'm not sure if I understood the code correctly or not, right now I'm doing this to use the token from the datastore:

err = getToken(ctx, &token)

if err != nil {
fmt.Fprint(w, "Remember to authenticate with fitbit")

return
}

// TODO: understand if this updates the token
client := fitbitConf.Client(ctx, &token)

but I'm not sure if this updates the token on the datastore

Patrick
--

Patrick

Aaron Torres

unread,
Dec 26, 2015, 6:34:30 PM12/26/15
to Patrick Guido, golang-nuts
 You're example looks correct to me -- you can also pass the client around after creating it in main and it should manage its token correctly so long as you initialize the token value with getToken().. The token itself will be replaced in long-term storage only if exchange is called explicitly or if the current token is invalid/expired at http request time.

This can become a lot more complicated if you're storing tokens for multiple users.

Patrick Guido

unread,
Dec 26, 2015, 6:39:10 PM12/26/15
to Aaron Torres, golang-nuts

Well, fortunately I need just one token for this application ^^

I'll do some tests tomorrow, thanks for the help, really appreciated :)

--

Patrick

Reply all
Reply to author
Forward
0 new messages