Skip to content

Commit

Permalink
Merge pull request #107 from spotify/update-get-profile-to-use-pkce
Browse files Browse the repository at this point in the history
update get profile demo to use more secure auth flow
  • Loading branch information
thisisjofrank authored Feb 28, 2023
2 parents 19e7810 + 3270774 commit fd309a4
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 17 deletions.
6 changes: 5 additions & 1 deletion get_user_profile/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ To run this demo you will need:

## Usage

Clone the repository, cd into the `get_user_profile` directory and run:
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.

Clone the repository, ensure that you are in the `get_user_profile` directory and run:

```bash
npm install
npm run dev
```

Replace the value for clientId in `/src/script.ts` with your own Client ID.
55 changes: 55 additions & 0 deletions get_user_profile/src/authCodeWithPkce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export async function redirectToAuthCodeFlow(clientId: string) {
const verifier = generateCodeVerifier(128);
const challenge = await generateCodeChallenge(verifier);

localStorage.setItem("verifier", verifier);

const params = new URLSearchParams();
params.append("client_id", clientId);
params.append("response_type", "code");
params.append("redirect_uri", "http://localhost:5173/callback");
params.append("scope", "user-read-private user-read-email");
params.append("code_challenge_method", "S256");
params.append("code_challenge", challenge);

document.location = `https://accounts.spotify.com/authorize?${params.toString()}`;
}

export async function getAccessToken(clientId: string, code: string) {
const verifier = localStorage.getItem("verifier");

const params = new URLSearchParams();
params.append("client_id", clientId);
params.append("grant_type", "authorization_code");
params.append("code", code);
params.append("redirect_uri", "http://localhost:5173/callback");
params.append("code_verifier", verifier!);

const result = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params
});

const { access_token } = await result.json();
return access_token;
}

function generateCodeVerifier(length: number) {
let text = '';
let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

async function generateCodeChallenge(codeVerifier: string) {
const data = new TextEncoder().encode(codeVerifier);
const digest = await window.crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode.apply(null, [...new Uint8Array(digest)]))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
22 changes: 6 additions & 16 deletions get_user_profile/src/script.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
// Because this is a literal single page application
// we detect a callback from Spotify by checking for the hash fragment
import { redirectToAuthCodeFlow, getAccessToken } from "./authCodeWithPkce";

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

if (!code) {
redirectToAuthCodeFlow(clientId);
} else {
const profile = await fetchProfile(code);
const accessToken = await getAccessToken(clientId, code);
const profile = await fetchProfile(accessToken);
populateUI(profile);
}

async function redirectToAuthCodeFlow(clientId: string) {
const params = new URLSearchParams();
params.append("client_id", clientId);
params.append("response_type", "token");
params.append("redirect_uri", "http://localhost:5173/callback");
params.append("scope", "user-read-private user-read-email");

document.location = `https://accounts.spotify.com/authorize?${params.toString()}`;
}

async function fetchProfile(code: string): Promise<UserProfile> {
const result = await fetch("https://api.spotify.com/v1/me", {
method: "GET", headers: { Authorization: `Bearer ${code}` }
Expand All @@ -41,5 +33,3 @@ function populateUI(profile: UserProfile) {
document.getElementById("url")!.setAttribute("href", profile.href);
document.getElementById("imgUrl")!.innerText = profile.images[0].url;
}

export { };

0 comments on commit fd309a4

Please sign in to comment.