Skip to content
DocumentationAPI Reference

Authentication

Learn how to authenticate with the Spotify Web API using different OAuth flows

Authentication is the first step to accessing the Spotify Web API. Spotify uses OAuth 2.0 for authorization, offering several flows depending on your application type and requirements.

Best for: Server-side applications that don’t need user data

  • Access public Spotify data (albums, artists, tracks, playlists)
  • No user login required
  • Cannot access user-specific endpoints
  • Tokens expire after 1 hour (no refresh)

Scopes define what your application can access. Request only the scopes you need.

ScopeDescription
user-read-privateRead user’s subscription details
user-read-emailRead user’s email address
playlist-read-privateRead user’s private playlists
playlist-modify-publicCreate and modify public playlists
playlist-modify-privateCreate and modify private playlists
user-library-readRead user’s saved tracks and albums
user-library-modifySave and remove tracks/albums
user-read-playback-stateRead player state
user-modify-playback-stateControl playback
user-read-currently-playingRead currently playing track
user-read-recently-playedRead recently played tracks
user-top-readRead user’s top artists and tracks

Use this flow for server-to-server authentication when you only need access to public Spotify data.

  1. Get your credentials

    Log into the Spotify Developer Dashboard and create an app to get your Client ID and Client Secret.

  2. Using the SDK with Client Credentials

    The SDK handles Client Credentials authentication automatically when you set environment variables:

    Terminal window
    export SPOTIFY_CLIENT_ID="your_client_id"
    export SPOTIFY_CLIENT_SECRET="your_client_secret"

    Then use the SDK directly:

    import Spotted from "spotted-ts";
    // SDK automatically handles token acquisition and refresh
    const client = new Spotted();
    // Get Taylor Swift's artist profile
    const artist = await client.artists.retrieve("06HL4z0CvFAxyc27GXpf02");
    console.log(artist.name, "-", artist.followers.total, "followers");

    Or pass credentials directly:

    import Spotted from "spotted-ts";
    const client = new Spotted({
    clientId: "your_client_id",
    clientSecret: "your_client_secret",
    });

Use this flow when your application needs to access user-specific data and can securely store a client secret.

  1. Redirect user to Spotify authorization

    const client_id = "your_client_id";
    const redirect_uri = "http://localhost:3000/callback";
    const scopes = [
    "user-read-private",
    "user-read-email",
    "playlist-read-private",
    "user-library-read",
    ].join(" ");
    function getAuthUrl() {
    const params = new URLSearchParams({
    response_type: "code",
    client_id: client_id,
    scope: scopes,
    redirect_uri: redirect_uri,
    state: generateRandomString(16), // CSRF protection
    });
    return `https://accounts.spotify.com/authorize?${params.toString()}`;
    }
    function generateRandomString(length) {
    const chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    return Array.from({ length }, () =>
    chars.charAt(Math.floor(Math.random() * chars.length)),
    ).join("");
    }
  2. Handle the callback and exchange code for tokens

    async function handleCallback(code) {
    const response = await fetch("https://accounts.spotify.com/api/token", {
    method: "POST",
    headers: {
    Authorization:
    "Basic " +
    Buffer.from(client_id + ":" + client_secret).toString("base64"),
    "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
    grant_type: "authorization_code",
    code: code,
    redirect_uri: redirect_uri,
    }),
    });
    const tokens = await response.json();
    // tokens contains: access_token, refresh_token, expires_in, token_type, scope
    return tokens;
    }
  3. Refresh the access token

    async function refreshAccessToken(refresh_token) {
    const response = await fetch("https://accounts.spotify.com/api/token", {
    method: "POST",
    headers: {
    Authorization:
    "Basic " +
    Buffer.from(client_id + ":" + client_secret).toString("base64"),
    "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token: refresh_token,
    }),
    });
    return response.json();
    }

Use this flow for mobile apps, single-page applications, or any client that cannot securely store a client secret.

  1. Generate PKCE code verifier and challenge

    function generateCodeVerifier() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return base64UrlEncode(array);
    }
    async function generateCodeChallenge(verifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(verifier);
    const digest = await crypto.subtle.digest("SHA-256", data);
    return base64UrlEncode(new Uint8Array(digest));
    }
    function base64UrlEncode(buffer) {
    return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
    }
  2. Redirect user with PKCE parameters

    async function getAuthUrlWithPKCE() {
    const codeVerifier = generateCodeVerifier();
    const codeChallenge = await generateCodeChallenge(codeVerifier);
    // Store codeVerifier securely (e.g., sessionStorage)
    sessionStorage.setItem("code_verifier", codeVerifier);
    const params = new URLSearchParams({
    response_type: "code",
    client_id: client_id,
    scope: scopes,
    redirect_uri: redirect_uri,
    state: generateRandomString(16),
    code_challenge_method: "S256",
    code_challenge: codeChallenge,
    });
    return `https://accounts.spotify.com/authorize?${params.toString()}`;
    }
  3. Exchange code for tokens using code verifier

    async function handleCallbackPKCE(code) {
    const codeVerifier = sessionStorage.getItem("code_verifier");
    const response = await fetch("https://accounts.spotify.com/api/token", {
    method: "POST",
    headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
    grant_type: "authorization_code",
    code: code,
    redirect_uri: redirect_uri,
    client_id: client_id,
    code_verifier: codeVerifier,
    }),
    });
    return response.json();
    }

When you need to access user-specific endpoints, pass an access token to the SDK:

import Spotted from "spotted-ts";
// After completing OAuth flow and getting tokens
const client = new Spotted({
bearerToken: userAccessToken,
});
// Access user-specific endpoints
const profile = await client.me.retrieve();
console.log(`Logged in as: ${profile.display_name}`);
// Get user's playlists
for await (const playlist of client.me.playlists.list()) {
console.log(playlist.name);
}
// Get user's saved tracks
for await (const item of client.me.tracks.list()) {
console.log(`${item.track.name} by ${item.track.artists[0].name}`);
}

The SDK handles token management internally, but if you’re managing tokens manually for user authentication, you can update the client:

// When token expires, get a new one from your OAuth flow
const newAccessToken = await refreshUserToken(refreshToken);
// Create a new client instance with the fresh token
const client = new Spotted({
bearerToken: newAccessToken,
});

Always handle authentication errors gracefully:

async function handleAuthError(response) {
if (!response.ok) {
const error = await response.json();
switch (error.error) {
case "invalid_grant":
// Refresh token expired or revoked
console.error("Please re-authenticate");
break;
case "invalid_client":
console.error("Invalid client credentials");
break;
case "invalid_request":
console.error(
"Missing or invalid parameters:",
error.error_description,
);
break;
default:
console.error("Authentication error:", error.error_description);
}
throw new Error(error.error_description);
}
return response.json();
}

Now that you understand authentication, explore these guides: