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

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.

Related

How to manage Spotify access token in express app

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

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 create Firebase token on server for use with unit tests?

I need to authenticate a Firebase user using node so I can test some server side methods. For each protected request, I verify the Firebase token using:
firebase.auth().verifyIdToken(firebaseAccessToken).then(function(decodedToken) {
// forward request
})
So in my test I created a token with a uid from my Firebase database
firebase.auth().createCustomToken(uid).then(function(token) {
//add to header for requests
})
Later I read that custom tokens are not verified by the verifyIdToken method, only client generated ones.
I've looked at this answer - server side verification of tokens in firebase
So I added databaseAuthVariableOverride to the init json
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: [dbURL],
databaseAuthVariableOverride: {
uid: [uid]
}
});
Still getting the output in my tests
Error: expected 200 "OK", got 401 "Unauthorized"
And the firebase error -
Error: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
So how do I emulate a user with my current setup?
Here's a Python script for generating Firebase ID tokens (not custom tokens).
python firebase_token_generator.py <UID>
There are probably easier ways to do this but you could call the Python script from Node.
You can generate a Firebase Id token from your custom token, then use that for verification. Eg:
const rp = require("request-promise");
// 'customToken' comes from FirebaseAdmin.auth().createCustomToken(uid)
function getIdTokenFromCustomToken(customToken) {
const url = `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=${API_KEY}`;
const data = {
token: customToken,
returnSecureToken: true
};
var options = {
method: "POST",
uri: url,
body: data,
json: true // Automatically stringifies the body to JSON
};
return rp(options)
// idToken is the firebase id token that can be used with verifyIdToken
.then(parsedBody => parsedBody.idToken)
.catch(function(err) {
// POST failed...
});
}

Save Token in local Storage using node

I'm using JWT ("jsonwebtoken": "^5.4.0") with express 4 and jade.
I'm able to create the right Token, but How can i Pass this token in each call?
Where I have to store this token ? in headers or in localStorage?
For now I'm using CURL with Postman, and Set token in header in
x-access-token
Have I Do create a middleware that retrieve a token from Database and use this in each call?
thanks
You do not need to save and check the token from the database. This token such a mechanism can be decoded with only your-server, and if it was done that the token is valid. The code that you want to do should look like.
var cookieParser = require('cookie-parser')
app.use(cookieParser())
app.get('/login', function(req, res, next) {
var user = {name:'test'}; //!! find the user and check user from db then
var token = jwt.sign(user, 'secret', {
expiresInMinutes: 1440
});
res.cookie('auth',token);
res.send('ok');
});
app.use(function(req, res, next) {
var token = req.cookies.auth;
// decode token
if (token) {
jwt.verify(token, 'secret', function(err, token_data) {
if (err) {
return res.status(403).send('Error');
} else {
req.user_data = token_data;
next();
}
});
} else {
return res.status(403).send('No token');
}
});
Here you can find very nice article : https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
I would recommend checking this out if you want local storage: https://www.npmjs.com/package/node-localstorage
But, with that said, you guys and girls wouldn't believe how long it took me to find res.cookie('auth' token) from the above answer. I scoured Google for hours, Passport docs, Express docs, GraphQL and authentication/authorization docs in an effort to find out how to get the token to the API in a stateless manner.
I already built JWT token security and secured my GraphQL resolvers with it, but then, I opted to use EJS along with graphql-request (approx same as Apollo Client), so I needed to find a way to pass the token to my middleware without using a server side session.
Storing a JWT token in cookies is fine especially if you take extra precautions such as signing the cookie, and I recall there are also options you can include that keep the cookie secure, so that other sites cannot see it if the "browser" allows access to cookies. If a cookie is signed with your server secret, the data inside the cookie simply cannot be altered and still be valid. The risk is always still someone leaking their token/cookie, and if that bothers you, do research into refresh tokens. However, API tokens are generally and should be kept tightly secret and safe. Your biggest annoyance will more likely be the requirement to maintain a blacklist of JWTs that expire a year from now if you set expiry to 1y.
I am just including my findings here because this question is actually a rare resource it seems...
Here is my Express middleware for authentication:
// AUTHENTICATION
app.use(async (req) => {
try {
const token = req.headers.authorization || req.cookies.auth
const { person } = await jwt.verify(token, SECRET)
req.person = person
return req.next()
} catch (e) {
return req.next()
}
})
You can see I am setting the token from the header with cookie as fallback. This supports my needs fine and allows me to use really any client with stateless security.
My logged in user is available as req.person in my views and GraphQL resolvers. If req.person is not set, the user is treated as not-logged-in.
I am using return req.next() which is important to note because calling next() without parameters is treated as "clean go-to next middleware and/or proceed to process request". If you include any string or object parameter, it will throw an error that can bubble down to your error handling middleware. You can try it yourself. Put return next('You are not authenticated.') in the catch block and you will see it halt the request before your route.
I use return next() because I handle authorization in the routes and in my resolvers. It allows more flexibility such as facilitating register and login mutations to be accessed by non-authenticated users.
Here is my GraphQL endpoint (I am using Apollo Server):
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
const context = {
person: req.person
}
return {
schema,
context,
rootValue: null
}
}))
In my GraphQL resolvers, the third parameter of every query has context.person populated with req.person which comes from the above Authentication middleware.
That is really all a person needs to know.
Here is how I am using the NPM package called graphql-request:
https://www.npmjs.com/package/graphql-request
app.get('/allpeople', async (req, res) => {
try {
const client = new GraphQLClient(GRAPHQL_ENDPOINT, {
headers: { Authorization: req.headers.authorization || req.cookies.auth }
})
const query = `query allPeople($serialNumber: String!) {
allPeople(serialNumber: $serialNumber) {
id
created
status
email
}
}`
const variables = {
serialNumber: req.person
}
const response = await client.request(query, variables)
res.render('allpeople/list', { people: response.allPeople })
} catch (e) {
throw [`allPeople`, `${JSON.stringify(error, null, 2)}`]
}
})
I include this code because there are no "more advanced" example usages of graphql-request, and I like it so far. It is very concise and could easily be swapped out for Apollo Client if you venture into React.js. My examples here are also very relevant for anyone researching createNetworkInterface and new ApolloClient().

Resources