Skip to content

Latest commit

 

History

History
110 lines (90 loc) · 3.3 KB

authflow.md

File metadata and controls

110 lines (90 loc) · 3.3 KB

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.