-
-
Save Uvacoder/27a67ee92e1aa7a89f7f25ee15d1bc54 to your computer and use it in GitHub Desktop.
Spotify refresh token rotation using NextAuth's Spotify provider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import NextAuth from 'next-auth/next'; | |
| import SpotifyProvider from 'next-auth/providers/spotify'; | |
| import type { Session } from 'next-auth'; | |
| import { JWT } from 'next-auth/jwt'; | |
| import axios from 'axios'; | |
| const SPOTIFY_REFRESH_TOKEN_URL = 'https://accounts.spotify.com/api/token'; | |
| const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID!; | |
| const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!; | |
| async function refreshAccessToken(token: JWT): Promise<JWT> { | |
| try { | |
| const basicAuth = Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64'); | |
| const { data } = await axios.post( | |
| SPOTIFY_REFRESH_TOKEN_URL, | |
| { | |
| grant_type: 'refresh_token', | |
| refresh_token: token.refreshToken, | |
| }, | |
| { | |
| headers: { | |
| Authorization: `Basic ${basicAuth}`, | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| }, | |
| } | |
| ); | |
| return { | |
| ...token, | |
| accessToken: data.access_token, | |
| accessTokenExpires: Date.now() + data.expires_in * 1000, | |
| }; | |
| } catch (error) { | |
| return { | |
| ...token, | |
| error: 'RefreshAccessTokenError', | |
| }; | |
| } | |
| } | |
| export const authOptions = { | |
| providers: [ | |
| SpotifyProvider({ | |
| clientId: SPOTIFY_CLIENT_ID, | |
| clientSecret: SPOTIFY_CLIENT_SECRET, | |
| // Scopes: | |
| // user-read-email | |
| // user-read-private | |
| // playlist-read-private | |
| // playlist-modify-public | |
| // user-top-read | |
| // user-read-recently-played | |
| authorization: `https://accounts.spotify.com/authorize?scope=user-read-email%20user-read-private%20playlist-read-private%20playlist-modify-public%20user-top-read%20user-read-recently-played`, | |
| }), | |
| ], | |
| callbacks: { | |
| async jwt({ token, account, user }: { token: JWT; account: any; user: any }) { | |
| if (account && user) { | |
| return { | |
| accessToken: account.access_token, | |
| refreshToken: account.refresh_token, | |
| accessTokenExpires: account.expires_at * 1000, | |
| user, | |
| }; | |
| } | |
| if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) { | |
| return token; | |
| } | |
| const newToken = await refreshAccessToken(token); | |
| return newToken; | |
| }, | |
| async session({ session, token }: { session: Session; token: JWT }) { | |
| session.accessToken = token.accessToken; | |
| session.error = token.error; | |
| session.user = token.user; | |
| return session; | |
| }, | |
| }, | |
| }; | |
| export default NextAuth(authOptions); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import NextAuth, { DefaultSession, Account as NextAuthAccount } from 'next-auth'; | |
| import { JWT as NextAuthJWT } from 'next-auth/jwt'; | |
| declare module 'next-auth' { | |
| interface Session extends DefaultSession { | |
| accessToken?: string; | |
| error?: string; | |
| } | |
| interface Account extends NextAuthAccount { | |
| expires_at: number; | |
| } | |
| } | |
| declare module 'next-auth/jwt' { | |
| interface JWT extends NextAuthJWT { | |
| accessToken?: string; | |
| refreshToken?: string; | |
| accessTokenExpires?: number; | |
| error?: string; | |
| user?: Session['user']; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment