I am building a dockerized REST API
app with the following structure:
../
web/
nginx/
dev.conf
Dockerfile-dev
client/
build/
conf/
Dockerfile-dev
node_modules/
package_json
public/
src/
App.jsx
components/
SpotifyRedirect.jsx
spotify-client/
Dockerfile-dev
node_modules
package-lock.json
package.json
authorization_code/
app.js
NOTE: In this project, user needs to go through two authorize/authenticate processes:
token
(this has been dealt
with already) another with Spotify
( which requires a redirect URI
and provides its own token
for its API access)
2a) So, at localhost
, either after localhost/auth/register
or localhost/auth/login
submit, I would have Spotify
redirect URI
(http://localhost:8888
), take me to this page:
2b) Then, clicking at the login button I would be asked to connect my app with Spotify, like so:
And with a last OK
there I would be granted permission and handed a token
which I could save and even refresh at my React
client.
The building blocks to this project were extracted from this tutorial:
using-spotifys-awesome-api-with-react
However, I already have configured a React
client
, which serves other purposes as well, apart from this authorization process.
The following is the relevant code which tries to integrate these two services: a more general client
and spotify-client
.
Relevant code:
So my first try was to create a specific service for spotify-client
, below client
service, exposing it to port 8888, like so:
docker-compose-dev.yml
nginx:
build:
context: ./services/nginx
dockerfile: Dockerfile-dev
restart: always
ports:
- 80:80
depends_on:
- web
- client
- spotify-client
client:
build:
context: ./services/client
dockerfile: Dockerfile-dev
volumes:
- './services/client:/usr/src/app'
- '/usr/src/app/node_modules'
ports:
- 3007:3000
environment:
- NODE_ENV=development
- REACT_APP_WEB_SERVICE_URL=${REACT_APP_WEB_SERVICE_URL}
depends_on:
- web
spotify-client: // NEW
build:
context: ./services/spotify-client
dockerfile: Dockerfile-dev
volumes:
- './services/spotify-client:/usr/src/app'
- '/usr/src/app/node_modules'
ports:
- 3000:8888
- 8888:3000
environment:
- NODE_ENV=development
- REACT_APP_WEB_SERVICE_URL=${REACT_APP_WEB_SERVICE_URL}
depends_on:
- web
- client
Then, I set each node process on its own Dockerfile
, like so:
client/Dockerfile-dev
# base image
FROM node:11.6.0-alpine
# set working directory
WORKDIR /usr/src/app
# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH
# install and cache app dependencies
COPY package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install [email protected] -g --silent
# start app
CMD ["npm", "start"]
and:
spotify-client/Dockerfile-dev // NEW
which has a different process running, as per required in Spotify
web docs:
# base image
FROM node:11.6.0-alpine
# set working directory
WORKDIR /usr/src/app/authorization_code
# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH
# install and cache app dependencies
COPY package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install [email protected] -g --silent
# start app <-- NOT npm start
CMD ["node", "app.js"]
My reverse proxy, I've tried:
nginx/dev.conf
server {
listen 80;
listen 8888; // NEW
location / {
proxy_pass http://client:3000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /auth { // <-- app authorization, not Spotify's
proxy_pass http://web:5000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
At my frontend I create a component
for my redirect link:
client/src/components/SpofityRedirect.jsx
import React, { Component } from 'react';
class SpotifyRedirect extends Component{
render(){
return (
<div className='SpotifyRedirect'>
<a href='http://localhost:8888'> Log in with Spotify </a>
</div>
);
}
}
export default SpotifyRedirect;
and here I show this redirect link at '/'.
client/src/App.jsx
import SpotifyRedirect from './components/SpotifyRedirect';
(...)
<Switch
<Route exact path='/' render={() => (
<SpotifyRedirect/>
)} />
(...)
</Switch>
More:
spotify-client/authorization_code/app.js
(this is provided by Spofity
, and I only inserted http://localhost:3000
)
var express = require('express'); // Express web server framework
var request = require('request'); // "Request" library
var cors = require('cors');
var querystring = require('querystring');
var cookieParser = require('cookie-parser');
var client_id = 'is'; // Your client id
var client_secret = 'secret'; // Your secret
var redirect_uri = 'http://localhost:8888'; // Your redirect uri
/**
* Generates a random string containing numbers and letters
* @param {number} length The length of the string
* @return {string} The generated string
*/
var generateRandomString = function(length) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
var stateKey = 'spotify_auth_state';
var app = express();
app.use(express.static(__dirname + '/public'))
.use(cors())
.use(cookieParser());
app.get('/login', function(req, res) {
var state = generateRandomString(16);
res.cookie(stateKey, state);
// your application requests authorization
var scope = 'user-read-private user-read-email user-read-playback-state playlist-modify-public playlist-modify-private';
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: client_id,
scope: scope,
redirect_uri: redirect_uri,
state: state
}));
});
app.get('/callback', function(req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
if (state === null || state !== storedState) {
res.redirect('/#' +
querystring.stringify({
error: 'state_mismatch'
}));
} else {
res.clearCookie(stateKey);
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: { 'Authorization': 'Bearer ' + access_token },
json: true
};
// use the access token to access the Spotify Web API
request.get(options, function(error, response, body) {
console.log(body);
});
// we can also pass the token to the browser to make requests from there
res.redirect('http://localhost:3000/#' + //NEW
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect('/#' +
querystring.stringify({
error: 'invalid_token'
}));
}
});
}
});
app.get('/refresh_token', function(req, res) {
// requesting access token from refresh token
var refresh_token = req.query.refresh_token;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({
'access_token': access_token
});
}
});
});
console.log('Listening on 8888');
app.listen(8888);
______
Docker
services at command line:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0e9870a7412c dev3_nginx "nginx -g 'daemon of…" 9 seconds ago Up 6 seconds 0.0.0.0:80->80/tcp dev3_nginx_1
e6bc5bbff630 dev3_spotify-client "node app.js" 26 minutes ago Up 26 minutes 0.0.0.0:3000->8888/tcp dev3_spotify-auth-server_1
a6b9e84953a3 dev3_client "npm start" 25 hours "/start.sh" 25 hours ago Up 25 hours 80/tcp, 0.0.0.0:3008->8080/tcp
16fb623ca2b3 dev3_web "/usr/src/app/entryp…" 25 hours
Finally, before build, I run:
$ export REACT_APP_WEB_SERVICE_URL=http://localhost
So far, with configuration aboce, when I click on Log in with Soptify, I'm getting:
QUESTION:
How can I use the configuration above with my nginx reverse proxy
in order to:
Spotify's
redirect uri http://localhost:8888
Problem: No container is listening on the port 8888. You can publish spotify-client:8888
directly on port 8888 (without nginx). Update docker-compose-dev.yml
:
spotify-client:
ports:
- 8888:8888
If you really need nginx, then you will need to play with nginx config + you need to publish nginx on the port 8888 as well. nginx/dev.conf
example:
server {
listen 80;
location / {
proxy_pass http://client:3000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location /auth {
proxy_pass http://web:5000;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
# reverse proxy on the port 8888 for spotify-client
server {
listen 8888;
location / {
proxy_pass http://<spotify-client service/ip>:<port>/;
proxy_redirect default;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
docker-compose-dev.yml
and nginx published ports:
nginx:
ports:
- 80:80
- 8888:8888
Generally, you need to configure properly nginx:8888
->spotify-client:port
.
IMHO: you don't need spotify-client
service at all. Just use implicit flow in your app to get Spotify token. It is more better choice for React/Angular (browser JS code). Keep in mind that refresh tokens doesn't exist in this flow, so you will need to implement silent refresh as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With