Table of contents
After using AWS Cognito provider for sign-in and sign-up, we have 2 set of the JWT. One is for the Strapi service and the other one is for the AWS service. If there is more and more third-party service, we need to handle multiple JWT one the frontend side. Resolving this situation, Strapi provides customize our own JWT verification which enables us to using a thrid-party JWT to access Strapi service.
Situation
Login method allowed: Strapi email login and Cognito OAuth
Identity: Email is unique for each user.
Preparation
Goto node_modules/strapi-plugin-user-permissions/config/policies/permissions.js
Paste to ./extensions/users-permissions/config/policies/permissions.js
Study
Let's check the original file.
'use strict';
const _ = require('lodash');
module.exports = async (ctx, next) => {
let role;
if (ctx.state.user) {
// request is already authenticated in a different way
return next();
}
if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
try {
const { id } = await strapi.plugins['users-permissions'].services.jwt.getToken(ctx);
if (id === undefined) {
throw new Error('Invalid token: Token did not contain required fields');
}
// fetch authenticated user
ctx.state.user = await strapi.plugins[
'users-permissions'
].services.user.fetchAuthenticatedUser(id);
} catch (err) {
return handleErrors(ctx, err, 'unauthorized');
}
if (!ctx.state.user) {
return handleErrors(ctx, 'User Not Found', 'unauthorized');
}
role = ctx.state.user.role;
...
And if you goto Strapi's repo, you may find that the fetching method is actually getting a user with role.
/**
* Promise to fetch authenticated user.
* @return {Promise}
*/
fetchAuthenticatedUser(id) {
return strapi.query('user', 'users-permissions').findOne({ id }, ['role']);
},
The flow basically is that:
- Check if header's authorization may obtain a valid Strapi JWT.
- If not, it return "Invalid token" or "unauthorized". If yes, it will fetch the user by id get from the JWT.
- After that get role from user and do the consequence checking on role authorization.
What we need to do is to add our own logic for the third party JWT verification after the exception is occur.
try {
const { id } = await strapi.plugins['users-permissions'].services.jwt.getToken(ctx);
if (id === undefined) {
throw new Error('Invalid token: Token did not contain required fields');
}
// fetch authenticated user
ctx.state.user = await strapi.plugins[
'users-permissions'
].services.user.fetchAuthenticatedUser(id);
} catch (err) {
//HERE IS OUR LOGIC
return handleErrors(ctx, err, 'unauthorized');
}
Customize
Since email is unique in this project, I will use it for fetching the user.
const jwt = require('jsonwebtoken');
...
try{
...
} catch (err) {
try{
const id_token = ctx.request.header.authorization.replace(/^Bearer\s/, '');
const tokenPayload = jwt.decode(id_token);
if(tokenPayload==null){
throw new Error('Invalid token: Token did not contain required fields');
}else{
var email = tokenPayload['email'];
ctx.state.user = await strapi.plugins[
'users-permissions'
].services.user.fetch({email}, ['role']);
}
} catch (error) {
return handleErrors(ctx, error, 'unauthorized');
}
}
The above code basically is doing the following:
- Decode the JWT
- Get the email from the JWT
- fetch the user with role by email
The fetch logic is just replace the id with email in our customize work which is similar to the fetchAuthenticatedUser(id) method.
/**
* Promise to fetch a/an user.
* @return {Promise}
*/
fetch(params, populate) {
return strapi.query('user', 'users-permissions').findOne(params, populate);
},
Result
After that, the Cognito JWT should be able to have the exact same result when calling API like the Strapi do.