Limit access of API token via policy

System Information
  • Strapi Version: v4.1.7
  • Operating System: macOS X 10.15.7
  • Database: mysql
  • Node Version: v14.18.2
  • NPM Version: 6.14.15
  • Yarn Version: 1.22.17

Hey all!

I am trying to write a policy which will pass through authenticated users or limit access to a known API token.
Therefore, I created a API token with the name development in the administration panel.

The policy looks like this:

"use strict";

/**
 * `is-dev-or-authenticated` policy.
 */

module.exports = (policyContext, config, { strapi }) => {
  strapi.log.info("In is-dev-or-authenticated policy.");

  if (policyContext.state.user) {
    // if a session is open
    // go to next policy or reach the controller's action
    return true;
  }

  const authHeader = policyContext.request.header.authorization;

  if (!authHeader) {
    return false;
  }

  const token = authHeader.split(" ")[1];

  console.log(token);
  console.log(strapi.admin.services.token.decodeJwtToken(token));

  return true;
};

As you can see, I am trying to get some information about the used API token in the request.
console.log(token); really returns the used token.
But console.log(strapi.admin.services.token.decodeJwtToken(token)); says { payload: null, isValid: false }

How can I get the information if the used token is the one with the name development?

Not sure if you’ve moved beyond this since it’s been over a year since you posted, but I found a way to do this.

You don’t need to decode the token at all, the policyContext has a policyContext.state.auth.credentials.name property, which is the name of the token provided. So in your case you could probably do something like this.

module.exports = (policyContext, config, { strapi }) => {
  strapi.log.info("In is-dev-or-authenticated policy.");
  var tokenName = policyContext.state.auth.credentials.name;

  if (policyContext.state.user || tokenName == "development") 
    return true;

  return false;
};

You might need to null check the credentials or the auth object, but this kind of solution has worked for me.

1 Like

Yeah and then you’re leaving backdoor to anyone who looks up the name of your token set on frontend & thus can access any route protected by that policy.
This is defeats the whole purpose of using JWT for authorization.

Here is the solution, following how users-permissions plugin (node_modules/@strapi/plugin-users-permissions/server/services/jwt.js) handles bearer token verification:

const jwt = require("jsonwebtoken");

module.exports = (policyContext, config, { strapi }) => {
  const authHeader = policyContext.request.header.authorization;

  if (!authHeader) {
    return false;
  }

  const token = authHeader.split(" ")[1];

  jwt.verify(
    token,
    strapi.config.get("plugin.users-permissions.jwtSecret"),
    {},
    (err, tokenPayload = {}) => {
      if (err) {
        return false;
      }
      // returns id of a user stored in database
      // console.log(tokenPayload);
      return true;
    }
  );
};

example of policy used (in src/extensions/users-permissions/strapi-server.js):

module.exports = (plugin) => {
  // Define the setup function in the user controller
  plugin.controllers.user.setuptest = async (ctx) => {
    // Your custom setup logic here
    ctx.body = "Hello World";
  };

  // Add the setup route directly to the plugin's routes
  plugin.routes["content-api"].routes.push({
    method: "GET", // or 'PUT', 'POST', etc., as per your requirement
    // needs to be /setup/create in order for koa.js to don't interpret new route as api/users/:id (for findOne)
    // I guess now it makes sense why there is a prefix by default
    path: "/users/setup/create", // The desired path for your setup route
    handler: "user.setuptest", // Points to the setup function in user controller
    config: {
      policies: ["global::is-authenticated"],
      prefix: "",
    },
  });

  return plugin;
};