Skip to content

Commit fd309a4

Browse files
Merge pull request #107 from spotify/update-get-profile-to-use-pkce
update get profile demo to use more secure auth flow
2 parents 19e7810 + 3270774 commit fd309a4

File tree

3 files changed

+66
-17
lines changed

3 files changed

+66
-17
lines changed

get_user_profile/readme.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ To run this demo you will need:
1212

1313
## Usage
1414

15-
Clone the repository, cd into the `get_user_profile` directory and run:
15+
Create an app in your [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/), set the redirect URI to ` http://localhost:5173/callback` and `http://localhost:5173/callback/` and copy your Client ID.
16+
17+
Clone the repository, ensure that you are in the `get_user_profile` directory and run:
1618

1719
```bash
1820
npm install
1921
npm run dev
2022
```
23+
24+
Replace the value for clientId in `/src/script.ts` with your own Client ID.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
export async function redirectToAuthCodeFlow(clientId: string) {
2+
const verifier = generateCodeVerifier(128);
3+
const challenge = await generateCodeChallenge(verifier);
4+
5+
localStorage.setItem("verifier", verifier);
6+
7+
const params = new URLSearchParams();
8+
params.append("client_id", clientId);
9+
params.append("response_type", "code");
10+
params.append("redirect_uri", "http://localhost:5173/callback");
11+
params.append("scope", "user-read-private user-read-email");
12+
params.append("code_challenge_method", "S256");
13+
params.append("code_challenge", challenge);
14+
15+
document.location = `https://accounts.spotify.com/authorize?${params.toString()}`;
16+
}
17+
18+
export async function getAccessToken(clientId: string, code: string) {
19+
const verifier = localStorage.getItem("verifier");
20+
21+
const params = new URLSearchParams();
22+
params.append("client_id", clientId);
23+
params.append("grant_type", "authorization_code");
24+
params.append("code", code);
25+
params.append("redirect_uri", "http://localhost:5173/callback");
26+
params.append("code_verifier", verifier!);
27+
28+
const result = await fetch("https://accounts.spotify.com/api/token", {
29+
method: "POST",
30+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
31+
body: params
32+
});
33+
34+
const { access_token } = await result.json();
35+
return access_token;
36+
}
37+
38+
function generateCodeVerifier(length: number) {
39+
let text = '';
40+
let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
41+
42+
for (let i = 0; i < length; i++) {
43+
text += possible.charAt(Math.floor(Math.random() * possible.length));
44+
}
45+
return text;
46+
}
47+
48+
async function generateCodeChallenge(codeVerifier: string) {
49+
const data = new TextEncoder().encode(codeVerifier);
50+
const digest = await window.crypto.subtle.digest('SHA-256', data);
51+
return btoa(String.fromCharCode.apply(null, [...new Uint8Array(digest)]))
52+
.replace(/\+/g, '-')
53+
.replace(/\//g, '_')
54+
.replace(/=+$/, '');
55+
}

get_user_profile/src/script.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
11
// Because this is a literal single page application
22
// we detect a callback from Spotify by checking for the hash fragment
3+
import { redirectToAuthCodeFlow, getAccessToken } from "./authCodeWithPkce";
34

4-
const clientId = "your-client-id-here"; // Replace with your client id
5-
const params = new URLSearchParams(window.location.hash.substring(1));
6-
const code = params.get("access_token");
5+
const clientId = "your_client_id";
6+
const params = new URLSearchParams(window.location.search);
7+
const code = params.get("code");
78

89
if (!code) {
910
redirectToAuthCodeFlow(clientId);
1011
} else {
11-
const profile = await fetchProfile(code);
12+
const accessToken = await getAccessToken(clientId, code);
13+
const profile = await fetchProfile(accessToken);
1214
populateUI(profile);
1315
}
1416

15-
async function redirectToAuthCodeFlow(clientId: string) {
16-
const params = new URLSearchParams();
17-
params.append("client_id", clientId);
18-
params.append("response_type", "token");
19-
params.append("redirect_uri", "http://localhost:5173/callback");
20-
params.append("scope", "user-read-private user-read-email");
21-
22-
document.location = `https://accounts.spotify.com/authorize?${params.toString()}`;
23-
}
24-
2517
async function fetchProfile(code: string): Promise<UserProfile> {
2618
const result = await fetch("https://api.spotify.com/v1/me", {
2719
method: "GET", headers: { Authorization: `Bearer ${code}` }
@@ -41,5 +33,3 @@ function populateUI(profile: UserProfile) {
4133
document.getElementById("url")!.setAttribute("href", profile.href);
4234
document.getElementById("imgUrl")!.innerText = profile.images[0].url;
4335
}
44-
45-
export { };

0 commit comments

Comments
 (0)