spotify
spotify copied to clipboard
Ensure clear example exists for how to use the client with existing access/refresh tokens
As it stands, there's no clear example for the following flow:
- User goes through oauth2 grant, access/refresh tokens are produced
- These tokens are persisted somewhere
- At a later date, the user makes a request to the service
- These tokens are then pulled out of persistence and used
Any update on this? I'm trying to figure out how to implement this flow in my application. I use a frontend to retrieve the user token and add it to a database. Then I want to read this token from the database and create a client using this token. Struggling to figure out a way to do this
Update: I figured this out. I pieced together some code from other examples:
spotifyToken := "tokenString"
token := &oauth2.Token{
AccessToken: spotifyToken,
}
httpClient := spotifyauth.New().Client(ctx, token)
client := spotify.New(httpClient)
spotifyToken is my token string which I read from the database. I have this code in a handler so I already has a ctx, but I believe you could just create a new background context like in some of the examples (context.Background())
The docs for an oauth2.Token have other fields for the token struct, one of which is a refresh token. I haven't looked into this yet but I'd guess it could handle refreshing for you
Yeah I'm struggling with this as well. I'm trying to just run a small app on a schedule, persisting client_id, client_secret, auth token, and refresh token as GitHub secrets. I'm setting the Expiry on the token to time.Now() when building a client:
`tok := &oauth2.Token{ AccessToken: "token string pulled from env", RefreshToken: "token string pulled from env", Expiry: time.Now(), }
client := spotify.New(spotifyAuth.Client(context.Background(), tok))`
But receive oauth2: cannot fetch token: 400 Bad Request Response: {"error":"invalid_grant","error_description":"Invalid refresh token"}
Part of the trouble with this is that I'm struggling to understand the refresh token system in Spotify's API. It says things like "A new refresh token might be returned too." in the documentation, which isn't helpful. Can I keep generating new clients from a static set of access + refresh tokens? The OAuth2 docs seem to imply I can with this line "The token will auto-refresh as necessary" here - https://pkg.go.dev/golang.org/x/oauth2#Config.Client
Yeah I'm struggling with this as well. I'm trying to just run a small app on a schedule, persisting client_id, client_secret, auth token, and refresh token as GitHub secrets. I'm setting the Expiry on the token to time.Now() when building a client:
`tok := &oauth2.Token{ AccessToken: "token string pulled from env", RefreshToken: "token string pulled from env", Expiry: time.Now(), }
client := spotify.New(spotifyAuth.Client(context.Background(), tok))`
But receive
oauth2: cannot fetch token: 400 Bad Request Response: {"error":"invalid_grant","error_description":"Invalid refresh token"}
Part of the trouble with this is that I'm struggling to understand the refresh token system in Spotify's API. It says things like "A new refresh token might be returned too." in the documentation, which isn't helpful. Can I keep generating new clients from a static set of access + refresh tokens? The OAuth2 docs seem to imply I can with this line "The token will auto-refresh as necessary" here - https://pkg.go.dev/golang.org/x/oauth2#Config.Client
If I recall correctly, they'll return a new refresh token when you refresh, so you'll need to come up with a way to write that back into the GitHub Action secret.
It might, but that timeout isn't made available. I've reused a refresh token longer than the life of the initial access token that generated it.
I solved this by re-implementing the code described here - https://developer.spotify.com/documentation/general/guides/authorization/code-flow/ under "Request a refreshed Access Token" and just running it on each execution and collecting the authorization code it returns.
If there is a way to do this using this lib + the oauth2 lib, I couldn't figure it out.
fwiw i construct the oauth2.Config by hand and it seems to refresh properly if I explicitly set AuthStyle: oauth2.AuthStyleInHeader
Which is what i understood the spotify docs to require for the refresh flow https://developer.spotify.com/documentation/web-api/tutorials/code-flow#request-a-refreshed-access-token
example:
https://github.com/seankhliao/earbug/blob/60fd546c490cb2518efa5851eb2eec0ef14a3ad0/subcommands/serve/auth.go#L99-L108
Simple example usage of "golang.org/x/oauth2"
for Spotify
Instruction
- Add
http://localhost:8080/callback"
to your Spotify's Applications "Redirect URL" - Run this code.
- Navigate to http://localhost:8080/auth in your web browser.
- Log In
Code
package main
import (
"context"
"golang.org/x/oauth2"
"log"
"net/http"
)
// tokenSource is a global variable that will be responsible for automatically refreshing the token when needed.
var tokenSource oauth2.TokenSource
// oauth2Config is the configuration for the OAuth2 flow, including the client ID, client secret, scopes, and endpoints.
var oauth2Config = oauth2.Config{
RedirectURL: "http://localhost:8080/callback",
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
Scopes: []string{"user-read-private", "user-read-email"},
Endpoint: oauth2.Endpoint{
AuthURL: "https://accounts.spotify.com/authorize",
TokenURL: "https://accounts.spotify.com/api/token"},
}
func main() {
// Registering the handlers for the authentication process.
http.HandleFunc("/auth", handleAuth)
http.HandleFunc("/callback", handleCallback)
// Starting the HTTP server on port 8080.
log.Fatal(http.ListenAndServe(":8080", nil))
}
// handleAuth initiates the OAuth2 authorization flow by redirecting the user to the provider's consent page.
func handleAuth(w http.ResponseWriter, r *http.Request) {
authURL := oauth2Config.AuthCodeURL("", oauth2.AccessTypeOffline)
http.Redirect(w, r, authURL, http.StatusFound)
}
// requestAccessToken exchanges the authorization code for an access token.
func requestAccessToken(code string) (*oauth2.Token, error) {
return oauth2Config.Exchange(context.Background(), code)
}
// handleCallback handles the callback from the OAuth2 provider. It extracts the authorization code from the request,
// exchanges it for an access token, and sets up a token source for automatic token refresh.
func handleCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "Missing code", http.StatusBadRequest)
return
}
token, err := requestAccessToken(code)
log.Printf("Token: %v", token) // Logging the token (for debugging purposes; be cautious in production).
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
return
}
// Create a token source that will automatically refresh the token as needed.
tokenSource = oauth2Config.TokenSource(context.Background(), token)
}