How to manage Spotify access token in express app - node.js

I'm writing an Express + Typescript application that, among other things, has an API endpoint which allows the user to search for a track. The endpoint receives a track name as a URL parameter, requests an access token from Spotify, uses the access token to query the Spotify Web API for matching tracks, then returns the data in JSON format. My problem is that I'm using the spotify-web-api-node library to interact with the API and so far I've only been able to make it work by requesting a new access token each time the route is accessed. I've tried adding middleware to request the access token elsewhere so it doesn't need to be done in this specific route, but it seemingly executes the route before the middleware, thus executing the API call without first getting the access token. Here is my working solution so far
import { Router, Request, Response, NextFunction } from 'express';
import SpotifyWebApi from 'spotify-web-api-node';
const spotifyRouter = Router();
const spotifyApi: SpotifyWebApi = new SpotifyWebApi({
clientId: /* My client ID */,
clientSecret: /* My client secret */
})
spotifyRouter.get('/search/track/:trackName', (req: Request, res: Response) => {
spotifyApi.clientCredentialsGrant()
.then(data => spotifyApi.setAccessToken(data.body['access_token']))
.then(() => { return spotifyApi.searchTracks(req.params.trackName) })
.then(data => res.json(data.body))
.catch(error => res.json(error));
})
export default spotifyRouter;
So to reiterate, I'm wondering how I can improve the management of the access token so it doesn't have to get a new one each time the above route is used.
Thanks.
EDIT: I forgot to mention that this is using Spotify's Client Credentials Flow

Related

How to only allow function to be ran on frontend after backend authenticates?

I have a frontend in react which displays two buttons currently one for authenticating and one for display data once authenticated
frontend:
const auth = async () => {
window.open("callbackApiRouteOnBackend")
}
const displayData = async () => {
// this function displays our data on frontend
}
return (
<>
<button onClick={auth}>Authenticate</button>
<button onClick={displayData}>Display data</button>
</>
)
authentication is done on the backend using oauth2 to get access token and refresh token this all works fine the only issue is right now the button can be clicked on frontend to display the data when the user isn't authenticated but it will not work as the fetch does not work as no access token. I get that I can use conditional rendering to hide the button when the user is authenticated but I cannot seem to figure out the way to determine on the frontend whether or not the user is authenticated
this is my backend to do the authentication in node js and express:
export const callback = async (req, res) => {
res.redirect("placeholder for external login page");
// then redirect to our redirect function to get access token
};
export const redirect = async (req, res) => {
const link = "external api link to get access token"
try {
const response = await axios.post(link);
// This is where it gets access token
// after access token is got how can i set a state to know the user is authenticated
// and then redirect them back to frontend with the frontend also knowing if
the user is authenticated or not
} catch (error) {
console.log(error);
}
res.redirect(307, "http://localhost:3000");
};
what is the best way to go about this because after the user is authenticated from backend I redirect to frontend but there is no way to tell on the backend or the frontend if the user is actually authenticated or not.
You can send the response to the front end:
res.status(200).send({
accessToken: response.access_token //Not sure what the response object looks like
});
From the front end set that token to localStorage or sessionStorage and check if the token is valid.
Read more: https://tasoskakour.com/blog/react-use-oauth2

Login with Facebook Flow in REST (Express.js with TypeScript + Passport.js) - few questions

i have few questions about login with facebook flow in REST application. I use Passport.js, Express.js, Typeorm and TypeScript. I don't implement client site yet.
First i created facebook strategy and i get a profile information in this place in code, but i want to process profile (check, save and generate JWT) in controller by use service layer.
passport.ts
const facebookStrategyCallback = async(accessToken: any, refreshToken: any, profile: any, done: any) => {
// I can get and process profile here, but i want to do it in controller use only
// service layer to check, save and generate JWT token.
done(null, profile);
};
This is my controller with facebook route and callback route:
facebookAuth = (request: Request, response: Response, next: NextFunction) => {
passport.authenticate('facebook')(request, response, next);
};
facebookAuthCallback = (request: Request, response: Response, next: NextFunction) => {
passport.authenticate('facebook', {
session: false,
failureRedirect: '/authenticate/fail'
}, (error, profile) => {
// process profile here using service functions
// But what do next?
})(request, response, next);
};
My flow for now is:
In client someone click to Login with Facebook (redirect to localhost:8080/oauth2/facebook)
User see facebook login form and fill it
User profile with query params CODE back to server site application localhost:8080/oauth2/facebook/callback?Code={somecode} (Is this correct way?)
...
And now what's next? What i should do with CODE get from (/ouath2/facebook/callback?CODE={somecodehere}). I can process profile save it into database, but how to back token to client? With redirect and query params? it's less security i guess.
Maybe i should set facebook callback url to localhost:3000/facebook/callback - but what i should do with this code from query params when i pass it back to server site?
Can someone write me a correct flow in REST application?

external api handling in the backend

On my express server I make queries to an external API using its own token. When I log in to my server I request a token to the external API based on the user who logged in, and I keep the token of the external API in the token of my express server.
Each user gets different data according to their token from the external api, for queries that require external API information, I read the received token and get the external API token to send it through headers with axios, for example:
const LoginUser = (request, response) {
axios.post('/ExternalApi/auth',request.body)
.then( data =>{
const payload = {
...
tokenExternalApi: data.token
}
const token = jwt.sign(payload, ...)
return response.status(200).json(token)
})
}
const getData = (req, response){
const tokenFromClient = req.headers.authorization
//Function extract tokenExternalApi from payload Token
const tokenExternalApi = getTokenExternl(tokenFromClient )
axios.get(`/urlExternalApi`, { headers:
{ Authorization: tokenExternalApi }}
).then(res => {
return response.status(200).json(res.data)
})
}
Is this the correct approach to managing external apis tokens or is there a cleaner way to do it?
Here is my sample code that I use for hit an external API within function in node js using axios
first time you should install axios npm install axois
const axios = require('axios');
async yourFunction(){
axios({
method: 'POST',
url: "http://yoururl.com",
data: {
name: '+62'+phoneNumber,
number: '+62'+phoneNumber,
message: 'success',
}
});
}
In my personal opinion, this seems to be a clean approach.
But keep in mind that tokens are visible to users, so the fact is your users can decode the token, view tokenExternalApi, know that you are using an external API in the backend and directly make calls to ExternalApi using that token, provided they have the know-how of it. If you understand this fact and are fine with it, then this works.
Otherwise, you can consider encoding the token before sending it to the user or store it on the server-side session.

Node.js Express Spotify API save in session

Question appeared while integrating Spotify API into Nodejs Express web application using spotify-web-api-node. How multiple simultaneous user requests should be handled? After passing the authentication step, user receives access_token, which is different for each user. Each request can have a session, for example using express-session since access_token is unique for each authenticated user. The weird thing is that I can't find an example with proper session usage in the description and samples https://www.npmjs.com/package/spotify-web-api-node where spotify-web-api-node is used. How is that possible to use global variable without session? Would it make full mess among separate user requests or I'm missing something? I guess that the access_token would be always replaced with latest authenticated user. Another usage example is here https://github.com/thelinmichael/spotify-web-api-node, though it also suggests to use one global instance.
the solution is to store the access_token and refresh_token after successful authentication in the session storage, than before calling Spotify API endpoints set both tokens for the current user from the present session:
saving tokens in the session after successful authentication:
app.get('/login', (req,res) => {
var scopes = [ ... ]
var authUrl = spotifyApi.createAuthorizeURL(scopes)
res.redirect(authUrl+"&show_dialog=true")
})
app.get('/callback', async (req, res) => {
const { code } = req.query
try {
var data = await spotifyApi.authorizationCodeGrant(code)
const { access_token, refresh_token } = data.body
spotifyApi.setAccessToken(access_token)
spotifyApi.setRefreshToken(refresh_token)
req.session.spotifyAccount = { access_token, refresh_token }
res.redirect('...')
} catch(err) {
res.send(`error ${err}`)
}
});
app.get('/userinfo', async (req,res) => {
try {
spotifyApi.setAccessToken(req.session.spotifyAccount["access_token"])
spotifyApi.setRefreshToken(req.session.spotifyAccount["refresh_token"])
var result = await spotifyApi.getMe()
console.log(result.body);
res.status(200).send(result.body)
} catch (err) {
res.status(400).send(err)
}
});
since access_token is only identification key which identifies any API request, that ensures that API endpoints are called for the current user. This technique prevents mess and confusion, so that each user can see and manipulate his data only.

How to verify accessToken in node/express using aws-amplify?

I am using AWS amplify for user authentication on my front-end React app.
My React app directly communicates with amplify without any backend(node server) interaction.
I have a REST API written in node/express. I want to secure that API using amplify.
Currently, I am planning to pass the access token from my react app to my node server. But I am unable to find a way through which I can verify this token on the backend using amplify.
Does aws-amplify package provide any function in which I can pass the access token to verify it?
Something like Auth.verifyToken(<access_token>)
Unfortunately, there is no such method available in official aws-amplify SDK.
After doing a lot of research I had to write my own middleware for that. This is not that difficult as it may seem but the only difficult part is to gather the right information from the huge AWS documentation.
I 've written this middleware to achieve the same, Hope this helps
import axios from 'axios'
import awsconfig from '../../aws-exports';
const COGNITO_URL = `https://cognito-idp.${awsconfig.aws_project_region}.amazonaws.com/`;
const authentication = async (req, res, next) => {
try {
const accessToken = req.headers.authorization.split(" ")[1];
const { data } = await axios.post(
COGNITO_URL,
{
AccessToken: accessToken
},
{
headers: {
"Content-Type": "application/x-amz-json-1.1",
"X-Amz-Target": "AWSCognitoIdentityProviderService.GetUser"
}
}
)
req.user = data;
next();
} catch (error) {
return res.status(401).json({
message: 'Auth failed'
});
}
};
export default authentication;
This middleware takes the authorization header and verifies the incoming accessToken using AWS Cognito REST API.
In order to get accessToken on your front-end you can do something like this:
const { accessToken: { jwtToken } } = await Auth.currentSession();
This jwtToken is your accessToken you can send this in your Authorization header and then verify this in the backend using the middleware I've written.
AWS actually has this documented pretty well. I have written a gist on a middleware I wrote to validate AWS Cognito tokens in my express.js server.
Essentially, when you create a User Pool in Amazon, AWS creates a JSON Web Key (JWK). The JWT contains a public key you can use to verify the signature of a JWT.
At a high level in Javascript:
import jwt from "jsonwebtoken";
const authToken = getJwtFromHeader(request);
// please remember to verify the "iss" claim and "exp" claim!
validateAuthToken(authToken);
// convert a jwk to a PEM for use by OpenSSL or crypto
const jwk = getJwkFromAWS();
const pem = jwkToPem(jwk);
// verify the signature of the token using the public key from AWS
await jwt.verify(authToken, pem, {algorithms: ['RS256']}, (err, decoded) =>{
console.log('decoded', decoded);
// TODO -- verify claims from decoded token
});
My GIST for a full express.js implementation:
https://gist.github.com/fourgates/92dc769468497863168417c3524e24dd
AWS Resources:
https://github.com/awslabs/aws-support-tools/tree/master/Cognito/decode-verify-jwt
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
Unfortunately, the aws-amplify SDK doesn't provide that functionality. For that reason, I've created an npm package to handle it.
How it works
The package exposes:
authenticate: An Express middleware that can be added to any route that needs to be authenticated against a Cognito User Pool.
authenticationError: An Express error handler that takes care of any authentication errors generated by the authenticate middleware.
JWTValidator: A class that can be instantiated to validate JWTs issued by Cognito. This is useful if you need a custom authentication and error handling logic. The authenticate middleware uses it internally.
Features included
JWT signature verification.
JWT claims verification.
Verify that the token is not expired.
Verify that the audience (aud) claim matches one of the valid
audiences provided in the configuration.
Verify that the issuer (iss) claim is valid for the configured user
pool.
Verify that the token_use claim matches one of the valid token uses
provided in the configuration.
Support for JWKs rotation as per described in the JWT signing key
rotation thread.
Ability to set custom pems for local testing without the need of
creating a User Pool.
Basic usage
// app.js
'use strict';
const express = require('express');
const { authenticate, authenticationError } = require('aws-cognito-express');
const app = express();
// Add the authentication middleware.
app.use(authenticate({
region: 'us-east-2',
userPoolId: 'us-east-2_6IfDT7ZUq',
tokenUse: ['id', 'access'],
audience: ['55plsi2cl0o267lfusmgaf67pf']
}));
// Protected route.
app.get('/articles', (req, res, next) => {
console.log('JWT payload: ', req.cognito);
});
// Add the authentication error handler.
app.use(authenticationError());
module.exports = app;
For more advanced use cases, please check the docs here.
Since 17 September 2021, we now have this repository (and associated npm package):
https://github.com/awslabs/aws-jwt-verify
https://www.npmjs.com/package/aws-jwt-verify
This allows us (external node applications, usually server side web facing applications) to verify JWTs signed by AWS, such as those emitted from AWS cognito.
Specifically, as the tokens are asymmetrically signed, this verified AWS account publisher of the node package refers to the AWS published JSON Web Key Set (JWKS), promoting a degree of trust in the code we use to verify the claims contained in JWTs as they may be presented as bearer tokens to our apps.
The sample code taken from: https://github.com/awslabs/aws-jwt-verify#express (in this case it uses express) appears to be quite user-friendly as well:
import express, { Request } from 'express';
import { CognitoJwtVerifier } from 'aws-jwt-verify';
const cognitoJWTVerifier = CognitoJwtVerifier.create({
userPoolId: 'your-user-pool-id', // mandatory, can't be overridden upon calling verify
tokenUse: 'access',
clientId: 'your-client-id',
});
app.get('/authenticated/route', async (req: Request & { verifiedCognitoJWT?: any }, res, next) => {
try {
const verifiedCognitoJWT = await cognitoJWTVerifier.verify(`${req.header('authorization')}`);
req.verifiedCognitoJWT = verifiedCognitoJWT;
next();
} catch (e) {
return res.status(401).json({ statusCode: 401, message: 'Unauthorized' });
}
}, getAuthenticatedRoute);
Note that you can optionally call hydrate() to pre-fetch the JWKS before your app begins listening for HTTP connections, but omitting this call just delays the first call to verify() which lazily fetches them.
And of course this example can be extended with proper typings, or built into a middleware etc. as required.

Resources