A Spotify OAuth flow in Remix: Using CookieSessionStorage
The first step in Spotify's Auth flow requires you to redirect the user to Spotify, after which Spotify will redirect the user right back to you, along with a code.
// remix > app > routes > login.tsx
import type { LoaderFunction } from "remix";
import { redirect } from "remix";
export const loader: LoaderFunction = async ({
request
}) => {
return redirect(
`https://accounts.spotify.com/authorize?client_id=${process.env.SPOTIFY_CLIENT_ID}&response_type=code&redirect_uri=http://localhost:3000/callback&scope=user-read-private%20user-library-read`);
}
We need to exchange the code Spotify sends back for an access_token
, the first of several interactions we'll be typing and refining using StepZen.
This step of authentication is the first (and only) that requires Basic Authentication, rather than the more commmon Bearer Authentication, and which
therefore demands a base64-encoded ID/password pair in its Authorization header, an invariant string value I store in my StepZen config and summon as $buffer
.
// stepzen > spotify > spotify.graphql
type Spotify_Auth {
access_token: String
token_type: String
expires_in: Int
refresh_token: String
scope: String
}
type Query {
get_token_with_code(
code: String!
): Spotify_Auth
@rest(
configuration: "spotify_config"
method: POST
contenttype: "application/x-www-form-urlencoded"
endpoint: "https://accounts.spotify.com/api/token?code=$code&grant_type=authorization_code&redirect_uri=http://localhost:3000/callback"
headers: [{
name: "Authorization",
value: "Basic $buffer"
}]
)
}
In the Loader for my /callback
, I grab the code
from the url and query an access token using the Fetch API.
// remix > app > routes > callback.tsx
const url = new URL(request.url);
const code = url.searchParams.get("code");
let res = await fetch(`${process.env.STEPZEN_ENDPOINT}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `${process.env.STEPZEN_API_KEY}`
},
body: JSON.stringify({
query: `
query MyQuery($code: String!) {
get_token_with_code(code: $code) {
access_token
}
}`,
variables: {
code: code,
},
}),
})
let data = await res.json();
That token is immediately extracted, set as a Cookie using getSession
, and persisted server-side using commitSession
.
// remix > app > routes > callback.tsx
let token = data.data.get_token_with_code.access_token;
const session = await getSession(
request.headers.get("Cookie")
);
session.set("token", token)
throw redirect(
"/tracks",
{
headers: {
'Set-Cookie': await commitSession(session),
},
}
Which will make the access_token
subsequently available to the loader at any route. Like my /tracks
route, to which we can now redirect immediately.
// remix > app > routes > callback.tsx
export default function Callback() {
return redirect('/tracks')
}
I walk through depaginating my Liked Songs history at /tracks
here.