Spotify Strategy
The Spotify strategy is used to authenticate users against a Spotify account. It extends the OAuth2Strategy.
The strategy supports refreshing expired access tokens. The logic for this is stolen from heavily inspired by remix-auth-supabase (opens in a new tab) – thanks @mitchelvanbever (opens in a new tab)!
Supported runtimes
Runtime | Has Support |
---|---|
Node.js | ✅ |
Cloudflare | ✅ |
Setup Guide
Create application in Spotify developer dashboard
- Go to the Spotify developer dashboard (opens in a new tab) and sign in with your account
- Click
Create an app
and give the application a suitable name and description - Click
Edit settings
and addhttp://localhost:3000
underWebsite
http://localhost:3000/auth/spotify/callback
underRedirect URIs
(remember to hitAdd
)
- Hit
Save
at the bottom of the modal - Grab your client ID and secret from the dashboard overview, and save them as env variables
Remember to update Website
and Redirect URIs
if/when deploying your app.
Install the package
npm install remix-auth-spotify
Setup ENV variables
# .env
SPOTIFY_CLIENT_ID="your client id"
SPOTIFY_CLIENT_SECRET="your client secret"
SPOTIFY_CALLBACK_URL="your callback url" # e.g. http://localhost:3000/auth/spotify/callback
Create the strategy instance
app/services/auth.server.ts
import { Authenticator } from "remix-auth";
import { SpotifyStrategy } from "remix-auth-spotify";
import { sessionStorage } from "~/services/session.server";
if (!process.env.SPOTIFY_CLIENT_ID) {
throw new Error("Missing SPOTIFY_CLIENT_ID env");
}
if (!process.env.SPOTIFY_CLIENT_SECRET) {
throw new Error("Missing SPOTIFY_CLIENT_SECRET env");
}
if (!process.env.SPOTIFY_CALLBACK_URL) {
throw new Error("Missing SPOTIFY_CALLBACK_URL env");
}
// See https://developer.spotify.com/documentation/general/guides/authorization/scopes
const scopes = ["user-read-email"].join(" ");
export const spotifyStrategy = new SpotifyStrategy(
{
clientID: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
callbackURL: process.env.SPOTIFY_CALLBACK_URL,
sessionStorage,
scope: scopes,
},
async ({ accessToken, refreshToken, extraParams, profile }) => ({
accessToken,
refreshToken,
expiresAt: Date.now() + extraParams.expiresIn * 1000,
tokenType: extraParams.tokenType,
user: {
id: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
image: profile.__json.images?.[0]?.url,
},
})
);
export const authenticator = new Authenticator(sessionStorage, {
sessionKey: spotifyStrategy.sessionKey,
sessionErrorKey: spotifyStrategy.sessionErrorKey,
});
authenticator.use(spotifyStrategy);
Setup your routes
app/routes/auth/spotify.tsx
import type { ActionArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { authenticator } from "~/services/auth.server";
export function loader() {
return redirect("/login");
}
export async function action({ request }: ActionArgs) {
return await authenticator.authenticate("spotify", request);
}
app/routes/auth/spotify.callback.tsx
import type { LoaderArgs } from "@remix-run/node";
import { authenticator } from "~/services/auth.server";
export function loader({ request }: LoaderArgs) {
return authenticator.authenticate("spotify", request, {
successRedirect: "/",
failureRedirect: "/login",
});
}
app/routes/logout.tsx
import type { ActionArgs } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { destroySession, getSession } from "~/services/session.server";
export async function action({ request }: ActionArgs) {
return redirect("/", {
headers: {
"Set-Cookie": await destroySession(
await getSession(request.headers.get("cookie"))
),
},
});
}
export function loader() {
throw json({}, { status: 404 });
}
Use the strategy
app/routes/index.tsx
import type { LoaderArgs } from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
import { spotifyStrategy } from "~/services/auth.server";
export async function loader({ request }: LoaderArgs) {
return spotifyStrategy.getSession(request);
}
export default function Index() {
const data = useLoaderData<typeof loader>();
const user = data?.user;
return (
<div style={{ textAlign: "center", padding: 20 }}>
<h2>Welcome to Remix!</h2>
<p>
<a href="https://docs.remix.run">Check out the docs</a> to get started.
</p>
{user ? (
<p>You are logged in as: {user?.email}</p>
) : (
<p>You are not logged in yet!</p>
)}
<Form action={user ? "/logout" : "/auth/spotify"} method="post">
<button>{user ? "Logout" : "Log in with Spotify"}</button>
</Form>
</div>
);
}