Permissions for Strapi Plugin

System Information

-Strapi Version: 4.1.5
-Operating System: Debian GNU/Linux 9
-Database: PostgreSQL 13
-Node Version: v14.16.0
-NPM Version: 6.14.11
-Yarn Version: v1.22.5

Hi everyone, I can’t seem to find consistent information on how to use permissions with a custom plugin. I want to make an endpoint available to my front-end application, but only when the front-end application has authenticated as a user and using the JWT that is returned from auth. I keep getting a 401 returned.

Here’s what I’m doing:

Hi everyone, I used this page to set up authentication in Strapi. I have a user created in Strapi, and from the front-end, I can authenticate and it returns a JWT token. When I set up collection types to only be accessible with the “authenticated” role, I can access those collection types in the api using this JWT token. So all of that works. The problem is that I can’t get this to work with my custom plugin, and I’m not sure why. I still get a 401 error instead.

Here’s how I set up the permissions:

Based on this page, I initially tried to leverage the isAuthenticated permission that the Users & Permissions plugin provides:

  {
    method: "GET",             
    path: "/progress",
    handler: "memberProgress.getProgress",
    config: {
        policies: ['plugins::users-permissions.isAuthenticated']
    },
  },

Unfortunately, this did not work. The server raised an error, saying that this could not be found. So back on the document linked above, I decided to take the approach of creating my own gloabl permission. I created src/policies/is-authenticated.js with the following contents:

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

  return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass
};

Then, I modified my plugin’s route as follows:

  {
    method: "GET",             
    path: "/progress",
    handler: "memberProgress.getProgress",
    config: {
        policies: ['global::is-authenticated']
    },
  },

This is all based on that document I linked to. Unfortunately, this still does not work. It seems to find the permission (server doesn’t raise an error about it), but when I try to access my plugin’s endpoint with the JWT token, I just get a 401 error.

Here is how I’m trying to access the endpoint on the front-end:

  // VERIFIED, auth works and I get the expected jwt
  const strapiAuth = await strapiApiAuth();  

  if ( strapiAuth && strapiAuth.hasOwnProperty("jwt") ) {
    
    try {      
      const response = await axios.get( 
        `${process.env.STRAPI_BACKEND_URL}/member-progress/progress?year=2022&name=&pageSize=10&page=1`,
        {
          headers: {           
            Accept: "application/json",                                                                                                                                                                            
            Authorization: `Bearer ${strapiAuth.jwt}`
          },                   
          timeout: 500,        
        }
      );
      console.log(response);
    } catch (error) {
      // This is where I land with the 401 error
      console.log(error);
    }
  }

I’m discovering he exact same problem trying to authenticate my custom plugin route and have been through the same docs. Did you find any solution to this?

Unfortunately I have not.

I have successfully implemented API protection with permissions. This is how I implemented it.

  1. I created the policy in server/policies/index.js:
module.exports = {
  checkConfigRoles(policyContext, _, { strapi }) {
    const configRoles = ['']; // read the roles from the config file
    const userRoles = policyContext.state.user.roles;
    const hasRole = userRoles.find((r) => configRoles.includes(r.code));
    if (hasRole) {
      return true;
    }

    return false;
  },
};
  1. I specified my policy in server/routes/index.js:
module.exports = [
  {
    method: "GET",
    path: "/myAPI",
    handler: "myAPI.runMyAPI",
    config: {
      policies: [`plugin::${myPluginId}.checkConfigRoles`],
    },
  },
];
  1. On the frontend, I use the provided admin/src/utils/axiosInstance.js. This is a file I already found when I generated the plugin:
import axios from 'axios';
import { auth } from '@strapi/helper-plugin';

const instance = axios.create({
  baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
});

instance.interceptors.request.use(
  async config => {
    config.headers = {
      Authorization: `Bearer ${auth.getToken()}`,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    };

    return config;
  },
  error => {
    Promise.reject(error);
  }
);

instance.interceptors.response.use(
  response => response,
  error => {
    // whatever you want to do with the error
    if (error.response?.status === 401) {
      auth.clearAppStorage();
      window.location.reload();
    }

    throw error;
  }
);

export default instance;

You can find a working solution in my plugin: https://github.com/gianlucaparadise/strapi-plugin-vercel-deploy/tree/592e85ac4cbcbf39096212896ef988a4ddcb5277

I having an issue with this same thing. Basically, I can’t reach the endpoint unless I add auth:false to the route (even if I add my jwt bearer token to the header of the request), and if I add auth:false to the route you no longer have access to policyContext.state.user object.

Are your plugin endpoints being used from inside the strapi GUI? or from an external location?

2 Likes

Hi all,

I’ve decided that it’s just not possible to do what I wanted to do. I wish the documentation was better, and that those who knew this system were more active on this forum. The best I can surmise is that endpoints written within a plugin are only accessible by that plugin. So there is simply no way to access an API endpoint within my plugin externally. Instead, I had to create a separate API endpoint in another location, following this guide: How to Create a Custom API Endpoint in Strapi.

I would like to be able to use DRY principles, but it’s not clear to me how to place the business logic in a place that can be shared by API endpoints in src/api/ and also by plugins in src/plugins/. So for now I have had to duplicate that logic.

1 Like

You can disable the authorization check and write your own policies inside the plugin

config: {
    auth: false
    policies: ['plugins::your-plugin.isAuthenticated']
 }
1 Like

Hi,
you should put type: ‘content-api’ in your routes file export. Then all routes from file will be available in admin/Users & Permissions plugin / Roles. All routes will be prefixed with /api (npm run strapi routes:list).

module.exports = {
  type: 'content-api',
  routes: [
    {
      method: 'POST',
      path: '/sensors',
      handler: 'sensor.create'
    }
  ]
}

1 Like

@branislavballon
this seems to be the right way but i get this error:
[ERROR]: strapi-server.js is invalid for ‘plugin::myplugin’.
this field has unspecified keys: type {}
any idea why?

As pointed out here, the snippet provided by @branislavballon needs to be used differently with current strapi:

module.exports = {
  'content-api': {
    type: "content-api",
    routes: [
      {
        method: 'POST',
        path: '/sensors',
        handler: 'sensor.create'
     }
  ]
}

It`s a shame that the docs does not very much cover authentication within custom plugins

2 Likes