I have a docker app running three services:
- client --> react frontend
- web --- > flask backend
- nginx ->- a reverse proxy for both
This is the (simplified) project structure:
docker-compose-dev.yml
services/
client/
src/
app.jsx
components/
spotify-auth.js
Spotify.jsx
nginx/
dev.conf
web/
Here is where I defined exposed ports at built time:
docker-compose-dev.yml
web:
build:
context: ./services/web
dockerfile: Dockerfile-dev
volumes:
- './services/web:/usr/src/app'
ports:
- 5001:5000 <----------------
environment:
- FLASK_ENV=development
- APP_SETTINGS=project.config.DevelopmentConfig
depends_on:
- web-db
nginx:
build:
context: ./services/nginx
dockerfile: Dockerfile-dev
restart: always
ports:
- 80:80 <----------------
- 8888:8888 <----------------
depends_on:
- web
- 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
REDIRECT
Client service, on its turn, needs to authenticate with Spotify, which requires a Redirect URI, whitelisted at https://developer.spotify.com. For mine, I have several options:

This is my nginx file, where I try to organize the right ports:
dev.conf
server {
listen 80;
listen 8888;
location / { // frontend at localhost:3000
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 /users { // backend at localhost:5000
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;
}
location /auth { # this authentication is for the app, not spotify
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;
}
}
Finally, there's my jsand jsx files used to:
localhost, or '/'spotify-auth.js
export const stateKey = 'spotify_auth_state';
export const client_id = 'my_client_id'; // Your client id
export const redirect_uri = 'http://localhost:3000'; // my redirect uri
//export const redirect_uri = 'http://localhost:8888'; // my second try for uri
export const scope ='user-read-private user-read-email user-read-playback-state playlist-modify-public playlist-modify-private';
Spotify.jsx
class SpotifyAuth extends Component {
constructor (props) {
super(props);
this.state = {
isAuthenticatedWithSpotify: false
};
this.state.handleRedirect = this.handleRedirect.bind(this);
this.loginSpotifyUser = this.loginSpotifyUser.bind(this);
};
function 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() {
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 (access_token && (state == null || state !== storedState)) {
alert('There was an error during the authentication');
} else {
localStorage.removeItem(stateKey);
}
// DO STUFF WITH ACCESS TOKEN HERE -- send ajax to backend routes
};
handleRedirect() {
const state = generateRandomString(16);
localStorage.setItem(stateKey, state);
let url = 'https://accounts.spotify.com/authorize';
url += '?response_type=token';
url += '&client_id=' + encodeURIComponent(client_id);
url += '&scope=' + encodeURIComponent(scope);
url += '&redirect_uri=' + encodeURIComponent(redirect_uri);
url += '&state=' + encodeURIComponent(state);
window.location = url;
// post data to backend
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/auth/spotify}`;
axios.post(url, data)
.then((res) => {
this.loginSpotifyUser(res.data.auth_token);
})
.catch((err) => { console.log(err); });
};
loginSpotifyUser(token) {
window.localStorage.setItem('spotifyAuthToken', token);
this.setState({ isAuthenticatedWithSpotify: true });
this.props.createMessage('Welcome to Spotify', 'success');
};
render() {
return (
<div className="button_container">
<button className="sp_button" onClick={this.handleRedirect}>
<strong>CONNECT YOUR SPOTIFY ACCOUNT</strong>
</button>
</div>
)
}
}
export default SpotifyAuth;
and render, like so:
App.jsx
render() {
return (
<div>
<Switch>
<Route exact path='/' render={() => (
<SpotifyAuth/>
)}
/>
</Switch>
</div>
ERROR:
After all of this is set and run, I get:
INVALID_CLIENT: Invalid redirect URI
LOGS:
Before building, I export this env variable:
$ export REACT_APP_WEB_SERVICE_URL=http://localhost
After services are built, I get the logs:
client_1 | You can now view client in the browser.
client_1 |
client_1 | Local: http://localhost:3000/
web_1 | * Environment: development
web_1 | * Debug mode: on
web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/0.chunk.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/bundle.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
nginx_1 | 172.21.0.1 - - [27/Mar/2019:03:58:56 +0000] "GET /static/js/main.chunk.js HTTP/1.1" 304 0 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
NOTE:
If I try and use localhost:8888 at Spotify.jsx, the app manages to authenticate with Spotify, but then all locations start with localhost:8888/auth/login and so on, which is not desired.
QUESTION:
Why won't localhost:3000, my client, work as redirect uri? What am I missing?
Is this the most reliable way of authenticating with Spotify on a docker project like this one?
Your issue:
Your docker-compose.yml has the client container configured with port mapping 3007:3000. Notice that Docker-compose port mapping is host:container (Compose file reference), this means that port 3007 of your host is mapping to port 3000 of the container.
This way, you're trying to connect to a port of your container that is not available in your host, although nginx is happy with it because it is on the same network as your client, so it can reach it and redirect the request.
If that is the case:
http://localhost:3000 doesn't work, because it is closed in the host side.http://localhost:3007 opens your client, because it is redirected to your client container, but you won't be able to use the Spotify auth there unless you whitelisted this URL and changed your redirect_uri.http://localhost:8888 opens your client, because you have nginx setup as reverse proxy, and it can access the client port 3000 because it's on the same network.Your solution:
Your solution is changing the docker-compose so client is mapped at ports 3000:3000. Then Spotify auth should be fine as the ports are open and the URLs are properly configured.
Extra:
Regarding your request for opinion on the design, nginx feels a bit underused by your design. If you set up a reverse proxy, is so that the services you're redirecting to are hidden in a secure network that cannot be accessed from outside. This way you can for example configure SSL on nginx and forget about HTTPs on the rest of the services. However, if such services can be accessed from other ports, it is of no use that you make this kind of configuration.
On a production setup, you would want to close the client and web ports from your docker-compose (literally, remove the port mapping. Nginx will have no problems accessing your containers as it's on the same network, unlike your host) and leave only nginx exposed to the real world.
You might also want to setup a rewrite rule where the client and server hang on http://localhost/client, http://localhost/server addresses respectively, but nginx rewrites the request and proxies it to the appropiate container so the containers actually see a request coming to http://localhost:3000/. You can see an example on how to configure all this at Stack Exchange - Nginx reverse proxy + URL rewrite.
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