This React Component below connects an app to Spotify using the Implicit Grant Flow, redirecting the app back to my client after token is obtained for user.
import React, { Component } from 'react';
import Credentials from './spotify-auth.js'
import './Spotify.css'
class SpotifyAuth extends Component {
constructor (props) {
super(props);
this.state = {
isAuthenticatedWithSpotify: false,
menu: this.props.userId.menu
};
this.state.handleRedirect = this.handleRedirect.bind(this);
};
generateRandomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
getHashParams() {
const hashParams = {};
const r = /([^&;=]+)=?([^&;]*)/g;
const q = window.location.hash.substring(1);
let e = r.exec(q);
while (e) {
hashParams[e[1]] = decodeURIComponent(e[2]);
e = r.exec(q);
}
return hashParams;
}
componentDidMount() {
//if (this.props.isAuthenticated) {
const params = this.getHashParams();
const access_token = params.access_token;
const state = params.state;
const storedState = localStorage.getItem(Credentials.stateKey);
localStorage.setItem('spotifyAuthToken', access_token);
localStorage.getItem('spotifyAuthToken');
if (window.localStorage.getItem('authToken')) {
this.setState({ isAuthenticatedWithSpotify: true });
};
if (access_token && (state == null || state !== storedState)) {
alert('Click "ok" to finish authentication with Spotify');
} else {
localStorage.removeItem(Credentials.stateKey);
}
// DO STUFF WITH ACCEES TOKEN HERE
this.props.onConnectWithSpotify(access_token);
};
handleRedirect(event) {
event.preventDefault()
this.props.createMessage('You linked your Spotify account!', 'success');
const params = this.getHashParams();
const access_token = params.access_token;
console.log(access_token);
const state = this.generateRandomString(16);
localStorage.setItem(Credentials.stateKey, state);
let url = 'https://accounts.spotify.com/authorize';
url += '?response_type=token';
url += '&client_id=' + encodeURIComponent(Credentials.client_id);
url += '&scope=' + encodeURIComponent(Credentials.scope);
url += '&redirect_uri=' + encodeURIComponent(Credentials.redirect_uri);
url += '&state=' + encodeURIComponent(state);
window.location = url;
};
render() {
return (
<div className="button_container">
<h1 className="title is-4"><font color="#C86428">Welcome</font></h1>
<div className="Line" /><br/>
<button className="sp_button" onClick={(event) => this.handleRedirect(event)}>
<strong>LINK YOUR SPOTIFY ACCOUNT</strong>
</button>
</div>
)
}
}
export default SpotifyAuth;
However, before redirect I would like to depict the following page, or a pop up, with defined scopes and 'agree' button:
According to Spotify docs for Implicit Flow, redirecting the user to https://accounts.spotify.com/authorize?client_id=5fe01282e94241328a84e7c5cc169164&redirect_uri=http:%2F%2Fexample.com%2Fcallback&scope=user-read-private%20user-read-email&response_type=token&state=123
...performs a couple of actions:
The user is asked to authorize access within the scopes. The Spotify Accounts >service presents details of the scopes for which access is being sought. If the user is not logged in, they are prompted to do so using their Spotify >username and password. When the user is logged in, they are asked to authorize access to the data sets >defined in the scopes.
None of the actions above happen with my code above when I call https://accounts.spotify.com/authorize?
, I just get the token.
What is wrong?
I have found this codepen example, where a user is prompted a log in page:
Spotify Implicit Grant Auth Popup
How do I add this functionality to my component above?
NOTE: Once you have accepted the scopes using one email id you wont be shown scope page again. Scopes are asked just once in the start. If you want to see scope page again then you have to authorize new email id.
I have created a react app to feel how your code is working. I ran the following code:
import React, { Component } from 'react';
export const authEndpoint = 'https://accounts.spotify.com/authorize';
class SpotifyAuth extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticatedWithSpotify: false
// menu: this.props.userId.menu
};
this.state.handleRedirect = this.handleRedirect.bind(this);
}
generateRandomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
getHashParams() {
const hashParams = {};
const r = /([^&;=]+)=?([^&;]*)/g;
const q = window.location.hash.substring(1);
let e = r.exec(q);
while (e) {
hashParams[e[1]] = decodeURIComponent(e[2]);
e = r.exec(q);
}
return hashParams;
}
componentDidMount() {
//if (this.props.isAuthenticated) {
const params = this.getHashParams();
const access_token = params.access_token;
const state = params.state;
const storedState = localStorage.getItem('stateKey');
localStorage.setItem('spotifyAuthToken', access_token);
localStorage.getItem('spotifyAuthToken');
if (window.localStorage.getItem('authToken')) {
this.setState({ isAuthenticatedWithSpotify: true });
}
if (access_token && (state == null || state !== storedState)) {
alert('Click "ok" to finish authentication with Spotify');
} else {
localStorage.removeItem('stateKey');
}
console.log(access_token);
// DO STUFF WITH ACCEES TOKEN HERE
// this.props.onConnectWithSpotify(access_token);
}
handleRedirect(event) {
event.preventDefault();
console.log('You linked your Spotify account!', 'success');
const params = this.getHashParams();
const access_token = params.access_token;
console.log(access_token);
const state = this.generateRandomString(16);
localStorage.setItem('stateKey', state);
// let url = 'https://accounts.spotify.com/authorize';
// url += '?response_type=token';
// url +=
// '&client_id=' + encodeURIComponent('f09fbf600009433dadce5836c57584c3');
// url += '&scope=' + encodeURIComponent('user-top-read');
// url += '&redirect_uri=' + encodeURIComponent('http://localhost:3000/abc');
// url += '&state=' + encodeURIComponent(state);
// url += '&show_dialog=true';
let url =
'https://accounts.spotify.com/authorize' +
'?response_type=code' +
'&client_id=f09fbf600009433dadce5836c57584c3' +
'&scope=' +
encodeURIComponent('user-read-private%20user-read-email') +
'&redirect_uri=' +
encodeURIComponent('http://localhost:3000/loginsuccess');
window.location = url;
}
render() {
return (
<div className="button_container">
<h1 className="title is-4">
<font color="#C86428">Welcome</font>
</h1>
<div className="Line" />
<br />
<button
className="sp_button"
onClick={(event) => this.handleRedirect(event)}
>
<strong>LINK YOUR SPOTIFY ACCOUNT</strong>
</button>
</div>
);
}
}
export default SpotifyAuth;
I believe that there is no problem with the code when I hardcoded the url instead of taking values from Credential
. I was able to see the scope page
It will be better if you could verify that scopes saved in Credential
are correct.
Also I have commented out this.props.createMessage
method which is abstract for me and might cause some issue.