Admin panel: 401 error on custom permissions with httponly cookie

System Information
  • Strapi Version: 3.0.2
  • Operating System: Mac os catalina 10.15.5
  • Database: Postgres
  • Node Version: v10.13.0
  • NPM Version: —
  • Yarn Version: 1.22.4

Hi,
Having issues with loggin as admin to the admin panel after setting “custom” policies and rules. I’ve followed this guide for setting up secure HttpOnly cookies for making requests and everything works as it should when I run it locally. I can log in to the panel and do everything I should be able to do. But I can’t login when in production.

I’ve extended permssions and made some simple policies to my new route.

When loggin on the /admin/auth/login i get redirected to admin/plugins/users-permissions/auth/login
and then back to /admin/auth/login with a failed attempt. Checking the console I get 401 error on these two endpoints /content-manager/content-types and user/me in the console.

Also this error on the webpage flashes before I’m redirected back to
/admin/auth/login again "an error occurred during models config fetch"

If I log in to the database (postgres hosted in google cloud) and run select * from strapi_administrator; I can see my admin account.

I saw another post Admin login permission error on v3.0.0-beta.17.4 · Issue #4373 · strapi/strapi · GitHub about role issues and if run this

SELECT * FROM “users-permissions_permission” WHERE action = 'init';

id | type | controller | action | enabled | policy | role
-----±------------------±-----------------±-------±--------±-------±-----
133 | users-permissions | userspermissions | init | t | | 1
136 | users-permissions | userspermissions | init | t | | 2

So I don’t think the issue could be this?

If i push a version to prod without custom permissions/routes (and no httpOnly cookie code) everthing works as it should. I can log in with the admin account and set upp new users/endpoint which I then can acces from the version with custom permissions if i push that one to prod. I can access the public data endpoints from both versions so I dont think there is an issue whit the database itself. It’s probably something with the code and permissions.

Any tips or pointers is greatly appriciated!

I have an auth file in extensions/users-permissions/controllers/Auth.js but i dont think the issue is in there as i have logs in it and they are not logging. Its pretty big but will post if necessary.

Below is my permissions file located in extensions/users-permissions/config/policies/permissions.js

const _ = require('lodash');

module.exports = async (ctx, next) => {
  if (ctx.state.user) {
    console.log("request is already authenticated in a different way", ctx.state.user);
    return next();
  }
  let role;
  
  if (ctx.request && ctx.request.header && !ctx.request.header.authorization) {
    const token = ctx.cookies.get("token");
    if (token) {
      ctx.request.header.authorization = "Bearer " + token;
    }
  }


  if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
    try {
      // I get the correct id if i compare the database . id = 1
      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
      // This is where i think it fails. The returned user is always null
      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;

    if (role.type === 'root') {
      return await next();
    }

    const store = await strapi.store({
      environment: '',
      type: 'plugin',
      name: 'users-permissions',
    });
    if (
      _.get(await store.get({ key: 'advanced' }), 'email_confirmation') &&
      !ctx.state.user.confirmed
    ) {
      return handleErrors(ctx, 'Your account email is not confirmed.', 'unauthorized');
    }

    if (ctx.state.user.blocked) {
      return handleErrors(
        ctx,
        'Your account has been blocked by the administrator.',
        'unauthorized'
      );
    }
  }

  // Retrieve `public` role.
  if (!role) {
    role = await strapi.query('role', 'users-permissions').findOne({ type: 'public' }, []);
  }
  const route = ctx.request.route;

  const permission = await strapi.query('permission', 'users-permissions').findOne(
    {
      role: role.id,
      type: route.plugin || 'application',
      controller: route.controller,
      action: route.action,
      enabled: true,
    },
    []
  );

  // Below is result of permission
/*   {
    type: 'users-permissions',
    controller: 'userspermissions',
    action: 'init',
    enabled: true,
    policy: '',
    role: 2
  }*/

  if (!permission) {
    return handleErrors(ctx, undefined, 'forbidden');
  }


  // Execute the policies.
  if (permission.policy) {
    //This is never fired
    return await strapi.plugins['users-permissions'].config.policies[permission.policy](ctx, next);
  }
  // Execute the action.
  await next();
};

const handleErrors = (ctx, err = undefined, type) => {
  console.log("handle errors type:", type);
  console.log("handle errors err:", err);
  console.log("handle errors ctx:", ctx);


  throw strapi.errors[type](err);
};

Other files
config/middleware.js

module.exports = () => ({
      settings: {
        cors: {
          enabled: true,
          headers: [
            "Content-Type",
            "Authorization",
            "X-Frame-Options",
            "access-control-allow-origin"
          ],
          credentials: true,
          origin: ["url-to-prod-env", "http://localhost:3213", "http://localhost:1338"],
        },
      },
    });

extensions/users-permissions/services/User.js

  module.exports = {
     // is always null?
      async fetchAuthenticatedUser(id) {
        return await strapi.query('user', 'users-permissions').findOne({ id }, ['role', 'avatar']);
      }
    };

It appears you are trying to link two very different auth methods. Users-Permissions doesn’t related to the Strapi admin at all and vice versa.

The guide you linked is referencing the users-permissions thus what we call “end user” routes. The admin itself uses passport.js. Each of these will need it’s own implementation of the httpcookie method.

Users-Permissions “end users” and Strapi Admins are two different types of users from two different plugins, one cannot auth with the other (yet).