Netlify Identity Role based Access control not working

To clarify this before I explain anything, I am using the Netlify free plan for this site, The site is Eleventy built and has the netlify identity widget in head.njk giving it to every page.

After reading this post, I have tried to impliment access control with netlify identity into my site. I have made redirect rules to redirect to a 404 if the user hasn’t been authenticated with the Netlify Identity widget.

_Redirects file in site root

/rates/*	200!	Role=admin

Netlify Identity widget found in every page.

<script async src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>

I have assigned roles to the relevant users in the Netlify UI. With no such luck. Is there any other setup needed?

Hey @kylesloper :wave:t2:

Phew! You read our 100+ post thread? :laughing: that’s awesome. Lots of good content in there.

Role-based access control with Netlify Identity can be… tricky. It’s why I effectively omitted it and instead implemented access control via front-end routing and authorized functions for any actual content in my React <-> Identity integration package, react-netlify-identity-gotrue, but that doesn’t much help you here! :stuck_out_tongue:

So without knowing your site domain or having an account / getting to poke around with API requests, I’ll just start by asking, once you added a role to a user, did you make sure to log out then back in from the site / user? Adding a role from the Netlify Admin UI doesn’t update the JWT already stored for the logged-in user, and the JWT is what contains the role information. Logging out and back in will pull down a fresh token with the role info baked in. Additionally, make sure you’re attempting to access the gated content within 1 hour of logging in. Anything beyond that and the token needs to be forcibly refreshed before accessing role-gated content. Since the _redirects role-gates operate via the JWT in the cookie and not the JWT in localstorage / JSON, this process can be difficult.

Hope that gets us started!


Jon

2 Likes

I’ll give you some details for if you want to do some further investigating.

Netlify domain

moonface.netlify.app

Git repo

Hey @kylesloper as @jonsully says it can be complicated but after much pain andsicussion with John I got it working in my protype app - with no client side routing. I’ve just made it open source in case it helps.

So first thing is you need to ensure the browser gets a redirect for the routes in question. This is done by adding another rule for 302.

In this case I have these roles:

  • none - access public home page
  • rdm - plus access to /rdm
  • applicant - plus access / applicant
  • admin - plus access to everyhting

I found all sorts of weird edge cases and even some browser specific issues. In the end the code encapsulates a working version that handles all the cases and browsers I threw at it.

It is also uses the free (starter) plan, is based on 11ty and I used Firefox for dev :slight_smile:

If I get time I’ll distill the auth / routing logic and do something like an egghead lesson.

Hope that helps

1 Like

Thanks for the detailed explanation man.

Just to be clear, do both rdm and applicant reassemble protected pages? I simply exchanged /rdm/* with pages of my own and it still doesn’t seem to work.

Yes, I used the same name for role and protected routes but thats not necessary.

If I recall correctly, applicant role is give to anyone who signs up but run is added manually using the netlify control panel.

Does the routing definitely get correctly deployed to the CDN?

1 Like

I don’t entirely know what you mean by if the routing is passed but I’ve just seen that no redirect rules are actually processed on deployment.

Deploy details | moonface (netlify.com)

Hey @kylesloper :wave:t2:

Sorry to be a little slow to get back to you! For starters, @slim and myself have done a lot of digging, playing, and research with role-gated _redirects over the past several months. They can work, but they do have pros and cons. There’s actually another thread going right now along the same lines. I’d recommend you give it a read -

That said, yes, the “No redirect rules processed” is definitely preventing you from even getting your foot in the door :stuck_out_tongue: Taking a look at your repository, the issue lies in the structure: A _redirects file needs to ultimately end up in your publish directory. You have it in your root directory but your netlify.toml shows a publish directory of ./_site. I see you’re using Eleventy, which, by default does not publish files it’s unaware of or not processing. To fix that, you’ll just need to add

eleventyConfig.addPassthroughCopy("_redirects");

To your .eleventy.js file :slight_smile:

Cheers!


Jon

1 Like

Thankyou @jonsully and @slim for your work with role based redirects.
I’ve added the passthrough into eleventy.js and now the UI shows that redirect rules are actually being processed.

Unfortunately still no luck with the actual role based access control since it will only redirect to the fallback method and take the user to the home page even when the user has been authenticated and has the relevant roles.

Just asking the baseline question here, but have you verified that the role is actually applied to the user? And logged out / back in to ensure that the new JWT is in the browser?


Jon

Yes I have. Even deleted and remade the user entirely.

Update

Now recieving this error in cli:

◈ Reloading redirect rules from [ '_site\\_redirects', '_redirects', 'netlify.toml' ]
◈ Reloading redirect rules from [ '_site\\_redirects', '_redirects', 'netlify.toml' ]
◈ Reloading redirect rules from [ '_site\\_redirects', '_redirects', 'netlify.toml' ]
◈ Reloading redirect rules from [ '_site\\_redirects', '_redirects', 'netlify.toml' ]
◈ Reloading redirect rules from [ '_site\\_redirects', '_redirects', 'netlify.toml' ]
◈ Reloading redirect rules from [ '_site\\_redirects', '_redirects', 'netlify.toml' ]
◈ Reloading redirect rules from [ '_site\\_redirects', '_redirects', 'netlify.toml' ]
 [Error: EPERM: operation not permitted, stat 'C:\Users\kyles\Desktop\moonface\_site\_redirects'] {
 errno: -4048,
 code: 'EPERM',
 syscall: 'stat',
 path: 'C:\\Users\\kyles\\Desktop\\moonface\\_site\\_redirects'
 }
 [Error: EPERM: operation not permitted, stat 'C:\Users\kyles\Desktop\moonface\_site\_redirects'] {
 errno: -4048,
 code: 'EPERM',
 syscall: 'stat',
 path: 'C:\\Users\\kyles\\Desktop\\moonface\\_site\\_redirects'
 }

Hey @kylesloper,
Sorry for the long delay here and thanks for bearing with us- you caught us over a very short-staffed chunk of the holidays. Thankfully, you were in great hands with @jonsully and @slim. I took a look at your site and see that you were successfully deploying redirect rules for a while, but not anymore. Have you decided to go the frontend routing route? Or should we dig into this issue with your redirects file?

1 Like

Yea I would appreciate if we were to be able to look into the issue with the redirects.

Great! To get back to a fresh slate on this issue (since there have been several deploys in the meantime), can you please add the redirects for Identity to your netlify.toml and share a link to your deploy log when that’s done? I’ll take a look at what our build system picked up (or didn’t) and check our logs for any errors.

1 Like

Yea sure. Quick question: is is necessary to use netlify.toml in this instance or is the normal _redirects file okay?

I have seen some issues where non-printing bytes prevent _redirects and _headers files from being uploaded (see:

and you also have to make sure they end up in the right published directory… so when debugging, I prefer to stick with netlify.toml in the root directory if it’s alright with you!

1 Like

Yea sure!

I added the redirects in like this, I never touch netlify.toml so It’s causing an error.

[build]
  publish = "_site"
  command = "eleventy"

# REDIRECT and HEADERS examples

# Redirect rule example
# For more information see:- https://www.netlify.com/docs/netlify-toml-reference/

#[[redirects]]
#  from = "/*"
#  to = "/blog/:splat"
# Two-step role-gate with fallback to home page
/rates/*	      200!  Role=rdm,admin
/rates/*   /    302!

# The default HTTP status code is 301, but you can define a different one e.g.
# status = 302

# Headers rule example
# For more information see:- https://www.netlify.com/docs/netlify-toml-reference/

#[[headers]]
#   Define which paths this specific [[headers]] block will cover.
#   for = "/*"

#[headers.values]
#   X-Frame-Options = "DENY"
#   X-XSS-Protection = "1; mode=block"
#   Content-Security-Policy = "frame-ancestors https://www.facebook.com"

# Redirects and headers are GLOBAL for all builds – they do not get scoped to
# contexts no matter where you define them in the file.
# For context-specific rules, use _headers or _redirects files, which are
# applied on a PER-DEPLOY basis.

Here is the error

◈ Reloading redirect rules from [ '_site\\_redirects', 'netlify.toml' ]
[SyntaxError: When resolving config file C:\Users\kyles\Desktop\moonface\netlify.toml:
Could not parse configuration file
Expected "#", "'", "[", "\"", "\n", "\r", [ \t], [A-Za-z0-9_\-] or end of input but "/" found.] {
  expected: [
    { type: 'literal', value: '#', description: '"#"' },
    { type: 'literal', value: "'", description: `"'"` },
    { type: 'literal', value: '[', description: '"["' },
    { type: 'literal', value: '"', description: '"\\""' },
    { type: 'literal', value: '\n', description: '"\\n"' },
    { type: 'literal', value: '\r', description: '"\\r"' },
    { type: 'class', value: '[ \\t]', description: '[ \\t]' },
    {
      type: 'class',
      value: '[A-Za-z0-9_\\-]',
      description: '[A-Za-z0-9_\\-]'
    },
    { type: 'end', description: 'end of input' }
  ],
  found: '/',
  offset: 293,
  line: 14,
  column: 1,
  type: 'userError'
}
[SyntaxError: When resolving config file C:\Users\kyles\Desktop\moonface\netlify.toml:
Could not parse configuration file
Expected "#", "'", "[", "\"", "\n", "\r", [ \t], [A-Za-z0-9_\-] or end of input but "/" found.] {
  expected: [
    { type: 'literal', value: '#', description: '"#"' },
    { type: 'literal', value: "'", description: `"'"` },
    { type: 'literal', value: '[', description: '"["' },
    { type: 'literal', value: '"', description: '"\\""' },
    { type: 'literal', value: '\n', description: '"\\n"' },
    { type: 'literal', value: '\r', description: '"\\r"' },
    { type: 'class', value: '[ \\t]', description: '[ \\t]' },
    {
      type: 'class',
      value: '[A-Za-z0-9_\\-]',
      description: '[A-Za-z0-9_\\-]'
    },
    { type: 'end', description: 'end of input' }
  ],
  found: '/',
  offset: 293,
  line: 14,
  column: 1,
  type: 'userError'
}

C:\Users\kyles\AppData\Roaming\npm\node_modules\netlify-cli\node_modules\netlify-redirector\lib\redirects.js:116
      throw ex;
      ^
abort({"message":"When resolving config file C:\\Users\\kyles\\Desktop\\moonface\\netlify.toml:\nCould not parse configuration file\nExpected \"#\", \"'\", \"[\", \"\\\"\", \"\\n\", \"\\r\", [ \\t], [A-Za-z0-9_\\-] or end of input but \"/\" found.","expected":[{"type":"literal","value":"#","description":"\"#\""},{"type":"literal","value":"'","description":"\"'\""},{"type":"literal","value":"[","description":"\"[\""},{"type":"literal","value":"\"","description":"\"\\\"\""},{"type":"literal","value":"\n","description":"\"\\n\""},{"type":"literal","value":"\r","description":"\"\\r\""},{"type":"class","value":"[ \\t]","description":"[ \\t]"},{"type":"class","value":"[A-Za-z0-9_\\-]","description":"[A-Za-z0-9_\\-]"},{"type":"end","description":"end of input"}],"found":"/","offset":293,"line":14,"column":1,"name":"SyntaxError","type":"userError"}) at Error
    at jsStackTrace (C:\Users\kyles\AppData\Roaming\npm\node_modules\netlify-cli\node_modules\netlify-redirector\lib\redirects.js:1070:13)
    at stackTrace (C:\Users\kyles\AppData\Roaming\npm\node_modules\netlify-cli\node_modules\netlify-redirector\lib\redirects.js:1087:12)
    at process.abort (C:\Users\kyles\AppData\Roaming\npm\node_modules\netlify-cli\node_modules\netlify-redirector\lib\redirects.js:8502:44)
    at process.emit (events.js:315:20)
    at processEmit [as emit] (C:\Users\kyles\AppData\Roaming\npm\node_modules\netlify-cli\node_modules\signal-exit\index.js:161:32)
    at processPromiseRejections (internal/process/promises.js:245:33)
    at processTicksAndRejections (internal/process/task_queues.js:94:32)
(Use `node --trace-uncaught ...` to show where the exception was thrown)

Ah no worries, that’s a formatting issue. For the netlify.toml, you’ll want to add them like this:

[[redirects]]
from = "/rates/*"
force = true
status = 200
conditions = {Role = ["rdm", "admin"]}

[[redirects]]
from = "/rates/*"
to = "/"
force = true
status = 302
1 Like

Here is the deploy log :))

Deploy details | moonface (netlify.com)