-
Notifications
You must be signed in to change notification settings - Fork 299
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
Ensure clear example exists for how to use the client with existing access/refresh tokens #167
Comments
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 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{ client := spotify.New(spotifyAuth.Client(context.Background(), tok))` But receive 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" |
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 example: |
Simple example usage of
|
I'll add my two cents because it took me a couple hours to turn the right dials and get it how I like it. I want my dependent route handlers to just call the package auth
import (
"context"
"fmt"
"net/http"
"os"
"strconv"
"time"
"github.com/zmb3/spotify/v2"
sa "github.com/zmb3/spotify/v2/auth"
"golang.org/x/oauth2"
)
var oauth2Config = oauth2.Config{
RedirectURL: "http://localhost:3000/auth/spotify/callback",
ClientID: os.Getenv("SPOTIFY_CLIENT_ID"),
ClientSecret: os.Getenv("SPOTIFY_SECRET"),
Scopes: []string{sa.ScopeUserReadEmail, sa.ScopePlaylistReadPrivate},
Endpoint: oauth2.Endpoint{
AuthURL: "https://accounts.spotify.com/authorize",
TokenURL: "https://accounts.spotify.com/api/token"},
}
func SpotifyLoginRedirect(w http.ResponseWriter, r *http.Request) {
authURL := oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline)
http.Redirect(w, r, authURL, http.StatusFound)
}
func requestAccessToken(code string) (*oauth2.Token, error) {
return oauth2Config.Exchange(context.Background(), code)
}
func SpotifyCallbackHandler(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "Missing code", http.StatusBadRequest)
return
}
tokenReq, err := requestAccessToken(code)
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
return
}
token, err := oauth2Config.TokenSource(context.Background(), tokenReq).Token()
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
return
}
cookieSettings := &http.Cookie{
Secure: false, // Set to true in production with HTTPS
HttpOnly: true,
Path: "/",
SameSite: http.SameSiteLaxMode,
}
accessCookie := *cookieSettings
accessCookie.Name = "spotify-access_token"
accessCookie.Value = token.AccessToken
accessCookie.Expires = token.Expiry
http.SetCookie(w, &accessCookie)
refreshCookie := *cookieSettings
refreshCookie.Name = "spotify-refresh_token"
refreshCookie.Value = token.RefreshToken
http.SetCookie(w, &refreshCookie)
expiryCookie := *cookieSettings
expiryCookie.Name = "spotify-token_expiry"
expiryCookie.Value = fmt.Sprintf("%d", token.Expiry.Unix())
http.SetCookie(w, &expiryCookie)
http.Redirect(w, r, "/", http.StatusFound)
}
func NewSpotifyClient(r *http.Request) (spotify.Client, error) {
access, err := r.Cookie("spotify-access_token")
if err != nil {
return spotify.Client{}, err
}
refresh, err := r.Cookie("spotify-refresh_token")
if err != nil {
return spotify.Client{}, err
}
expiry, err := r.Cookie("spotify-token_expiry")
if err != nil {
return spotify.Client{}, err
}
expiryUnix, err := strconv.ParseInt(expiry.Value, 10, 64)
if err != nil {
return spotify.Client{}, err
}
expiryTime := time.Unix(expiryUnix, 0)
oauthClient := oauth2.NewClient(
r.Context(),
oauth2.StaticTokenSource(
&oauth2.Token{
AccessToken: access.Value,
TokenType: "Bearer",
RefreshToken: refresh.Value,
Expiry: expiryTime},
))
spotClient := spotify.New(oauthClient)
return *spotClient, nil
} And then in the route handler (a bit contrived but once you have the client object, you should know what to do)... import (
"net/http"
"github.com/nathan-hello/playlist-powertools/src/auth"
"github.com/zmb3/spotify/v2"
)
func PlaylistCurate(w http.ResponseWriter, r *http.Request) {
playlists := []*spotify.SimplePlaylistPage{}
client, err := auth.NewSpotifyClient(r)
if err != nil {
http.Redirect(w, r, "/auth/spotify", http.StatusFound)
}
p1, err := client.CurrentUsersPlaylists(r.Context(), spotify.Offset(0))
if err != nil {
w.Write([]byte(err.Error()))
}
playlists = append(playlists, p1)
for offset := 50; offset < int(p1.Total); offset += 50 {
currentPage, err := client.CurrentUsersPlaylists(r.Context(), spotify.Offset(offset))
if err != nil {
w.Write([]byte(err.Error()))
}
playlists = append(playlists, currentPage)
}
_, err = w.Write([]byte("200"))
if err != nil {
panic(err)
}
} |
As it stands, there's no clear example for the following flow:
The text was updated successfully, but these errors were encountered: