Files
mintlify__docs/tutorials/authorization-code-pkce.mdx
Mintlify Agent 65fd3621dc Add Spotify Web API documentation
- Created 111 MDX documentation files
- Added 89 API endpoint pages across 14 categories
- Included 11 concept guides and 7 tutorials
- Configured Mintlify with Spotify branding (#1DB954)
- All pages validated successfully
2026-02-07 11:47:26 +00:00

305 lines
8.2 KiB
Plaintext

---
title: "Authorization Code with PKCE"
description: "Implement PKCE flow for mobile and single-page applications"
---
# Authorization Code with PKCE tutorial
PKCE (Proof Key for Code Exchange) is an extension to the Authorization Code Flow that provides additional security for applications that can't securely store a client secret.
## When to use PKCE
Use this flow for:
- Single-page applications (SPAs)
- Mobile applications
- Desktop applications
- Any public client that can't secure a client secret
## How PKCE works
PKCE adds two parameters to the authorization flow:
- **Code verifier**: Random string generated by your app
- **Code challenge**: SHA256 hash of the code verifier
This prevents authorization code interception attacks.
## Implementation
### Step 1: Generate code verifier and challenge
```javascript
// Generate random code verifier
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64URLEncode(array);
}
// Create code challenge from verifier
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(hash));
}
// Base64 URL encoding
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
```
### Step 2: Build authorization URL
```javascript
const clientId = 'your_client_id';
const redirectUri = 'https://yourapp.com/callback';
const scope = 'user-read-private user-read-email';
// Generate PKCE parameters
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store verifier for later use
localStorage.setItem('code_verifier', codeVerifier);
// Build authorization URL
const authUrl = new URL('https://accounts.spotify.com/authorize');
authUrl.searchParams.append('client_id', clientId);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('redirect_uri', redirectUri);
authUrl.searchParams.append('scope', scope);
authUrl.searchParams.append('code_challenge_method', 'S256');
authUrl.searchParams.append('code_challenge', codeChallenge);
// Redirect user
window.location.href = authUrl.toString();
```
### Step 3: Handle callback
```javascript
// Extract code from URL
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) {
// Retrieve stored code verifier
const codeVerifier = localStorage.getItem('code_verifier');
// Exchange code for token
await exchangeCodeForToken(code, codeVerifier);
}
```
### Step 4: Exchange code for token
```javascript
async function exchangeCodeForToken(code, codeVerifier) {
const clientId = 'your_client_id';
const redirectUri = 'https://yourapp.com/callback';
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: clientId,
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri,
code_verifier: codeVerifier
})
});
const data = await response.json();
// Store tokens
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
// Clean up code verifier
localStorage.removeItem('code_verifier');
return data;
}
```
## Complete React example
```javascript
import React, { useEffect, useState } from 'react';
// Configuration
const CLIENT_ID = 'your_client_id';
const REDIRECT_URI = 'http://localhost:3000/callback';
const SCOPES = 'user-read-private user-read-email';
// PKCE utilities
function generateCodeVerifier() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return base64URLEncode(array);
}
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await window.crypto.subtle.digest('SHA-256', data);
return base64URLEncode(new Uint8Array(hash));
}
function base64URLEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
function App() {
const [token, setToken] = useState(null);
const [profile, setProfile] = useState(null);
useEffect(() => {
// Check for callback code
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) {
handleCallback(code);
} else {
// Check for existing token
const storedToken = localStorage.getItem('access_token');
if (storedToken) {
setToken(storedToken);
fetchProfile(storedToken);
}
}
}, []);
async function handleCallback(code) {
const codeVerifier = localStorage.getItem('code_verifier');
try {
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: CLIENT_ID,
grant_type: 'authorization_code',
code: code,
redirect_uri: REDIRECT_URI,
code_verifier: codeVerifier
})
});
const data = await response.json();
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
localStorage.removeItem('code_verifier');
setToken(data.access_token);
// Clean URL
window.history.replaceState({}, document.title, '/');
// Fetch profile
fetchProfile(data.access_token);
} catch (error) {
console.error('Error exchanging code:', error);
}
}
async function login() {
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
localStorage.setItem('code_verifier', codeVerifier);
const authUrl = new URL('https://accounts.spotify.com/authorize');
authUrl.searchParams.append('client_id', CLIENT_ID);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('redirect_uri', REDIRECT_URI);
authUrl.searchParams.append('scope', SCOPES);
authUrl.searchParams.append('code_challenge_method', 'S256');
authUrl.searchParams.append('code_challenge', codeChallenge);
window.location.href = authUrl.toString();
}
async function fetchProfile(accessToken) {
const response = await fetch('https://api.spotify.com/v1/me', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
const data = await response.json();
setProfile(data);
}
function logout() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
setToken(null);
setProfile(null);
}
if (!token) {
return (
<div>
<h1>Spotify PKCE Example</h1>
<button onClick={login}>Login with Spotify</button>
</div>
);
}
return (
<div>
<h1>Welcome, {profile?.display_name}</h1>
<p>Email: {profile?.email}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
export default App;
```
## Security considerations
<AccordionGroup>
<Accordion title="Generate strong code verifier">
Use cryptographically secure random generation for the code verifier.
</Accordion>
<Accordion title="Store verifier securely">
Store the code verifier in a secure location. For web apps, sessionStorage is better than localStorage for sensitive data.
</Accordion>
<Accordion title="Clean up after exchange">
Always remove the code verifier after exchanging for a token.
</Accordion>
<Accordion title="Use HTTPS">
Always use HTTPS for your redirect URI.
</Accordion>
</AccordionGroup>
## Next steps
<CardGroup cols={2}>
<Card title="Refreshing tokens" href="/tutorials/refreshing-tokens">
Handle token expiration
</Card>
<Card title="Authentication flows" href="/getting-started/authentication-flows">
Compare different flows
</Card>
</CardGroup>