Netlify Identity - Selective branches access control

Hi, I am trying to protect selective branches from traffic redirecting unauthorized users to another login site. I have enabled Netlify Identity on Login Site, invited myself and gave reviewer role to my user. Then I have created simple script to write _redirects file based on custom logic/branches, ending up with this:

/* /index.html 200! Role=reviewer

/* https://XXXX.netlify.app/login?site=https://deploy-preview-564--XXXX.netlify.app/:splat 302!

Then in Login Site I made a simple login which returns me Netlify Identity session with access_token and so on. I use that token to generate a cookie:

const cookie = require('cookie');

exports.handler = (event, context, callback) => {
  const {token} = JSON.parse(event.body);

  const expiry = Math.floor(Date.now() / 1000) + 60 * 60;

  const netlifyCookie = cookie.serialize('nf_jwt', token, {
    secure: true,
    path: '/',
    expires: new Date(expiry.toString()),
  });

  const response = {
    jwt: token,
    exp: expiry,
  };

  callback(null, {
    statusCode: 200,
    headers: {
      'Set-Cookie': netlifyCookie,
      'Cache-Control': 'no-cache',
    },
    body: JSON.stringify(response),
  });
};

After storing the cookie in users cookies I redirect user to the redirect function:

const parseURL = require('url-parse');
const cookie = require('cookie');

exports.handler = function (event, context, callback) {
  const {referer} = event.headers;

  const {site} = event.queryStringParameters;

  const urlData = parseURL(site);
  const siteOrigin = urlData.origin;

  //check that cookies are present
  const {headers} = event;
  const cookieHeader = headers.cookie || '';
  const cookies = cookie.parse(cookieHeader);

  if (cookieHeader === '' || !cookies.nf_jwt) {
    const redirectToURL = referer.match(/\?site=/g) ? referer : `${referer}?site=${site}`;

    return callback(null, {
      statusCode: 302,
      headers: {
        Location: redirectToURL,
        'Cache-Control': 'no-cache',
      },
      body: JSON.stringify({message: 'Token is not present'}),
    });
  }

  callback(null, {
    statusCode: 302,
    headers: {
      Location: `${siteOrigin}/.netlify/functions/set-cookie?token=${cookies.nf_jwt}&site=${site}`,
      'Cache-Control': 'no-cache',
    },
    body: null,
  });
};

This redirect invokes set-cookie function from the Gated Site:

const cookie = require('cookie');

exports.handler = (event, context, callback) => {
  const {token, site} = event.queryStringParameters;

  const twoWeeks = 14 * 24 * 3600000;

  const netlifyCookie = cookie.serialize('nf_jwt', token, {
    secure: true,
    httpOnly: true,
    path: '/',
    maxAge: twoWeeks,
  });

  const html = `
  <html lang="en">
    <head>
      <meta charset="utf-8">
    </head>
    <body>
      <noscript>
        <meta http-equiv="refresh" content="0; url=${site}" />
      </noscript>
    </body>
    <script>
      setTimeout(function(){
        window.location.href = ${JSON.stringify(site)}
      }, 0)
    </script>
  </html>`;

  callback(null, {
    statusCode: 200,
    headers: {
      'Set-Cookie': netlifyCookie,
      'Cache-Control': 'no-cache',
      'Content-Type': 'text/html',
    },
    body: html,
  });
};

The result is redirect back to login after successful Netlify Identity login - set-cookie returns 302 and seems like the redirect from _redirects is triggered, like if Role condition was not met. I have decoded the token and roles are ok. Requests sent from login action up to the redirect back to the login:

...
"app_metadata": {
  "provider": "email",
  "roles": [
    "reviewer"
  ]
},
...

I went through all Netlify docs and articles about the access control and I’m stuck here. One thing I noticed is that Netlify Identity token returns app_metadata.roles but docs about JWT access control says it should be app_metadata.authorization.roles - I could not map the token since I have no secret from Netlify Identity.

Thanks, help would be greatly apprieciated!

Disclaimer: I can only share the site to Netlify representatives.

Hi @AverageNetlifyBoi,

This will not work as both of those sites have a different JWT token. So the second site signs the token with a different secret than the one that’s trying to decode it. Thus, the roles never match. I believe on the Business plan you could set a custom JWT token, so if you sync the tokens on both sites, it should work.

I have set that - the same JWT Secret (Access Control) for both sites and behaviour is exactly the same.

In that case, the site name would be useful.

Hi @AverageNetlifyBoi,

I can see that you shared the site. However, that results in an infinite redirect for me. Would it be possible for you to add me or create a shared account for the Netlify Support Team so we could check this happening instead of trying to guess by reading the text description? You could add the login details to the message you sent.

About the confusion on roles, app_metadata.roles is a valid configuration.

Login site has credentials already entered. I have tried to manually set a nf_jwt cookie for the gated site and it still redirects to the login meaning that RBAC validation fails.

/* 200! Role=reviewer

/* https://LOGIN_SITE.netlify.app/login?site=https://GATED_SITE.netlify.app/:splat 302!

That is my concern right now, why the validation fails - token has expiration date in an hour, logged in via GATED_SITE Netlify Identity, cookie is there, http only, secure, not using custom JWT secret since I’m trying to use pure Netlify Identity, user has the role - I don’t know what is failing :thinking:

Hey @AverageNetlifyBoi,

As I said, if you can share a login for the Support Team or generate a cookie value for us with the JWT secret that you’ve set, we can do some debugging on our end.

I’ve reverted the changes, you can try now - I was trying some stuff :smile:

Hey @AverageNetlifyBoi :wave: ,

Thanks for reverting the changes but when I visit the site it redirects and throws a 404.

As Hrishikesh has requested, could you share a login for the Support Team, or generate a cookie value for us with the JWT secret that you’ve set to help us debug further please? :slight_smile: