Redwood Tutorial Blog w/ Netlify Identity and RBAC Example

I recently deployed a version of the RedwoodJS Blog Tutorial but with some enhancements to demo Netlify Identity Authentication and Role Based Access Control (RBAC).

Here’s the demo.

And here’s the GitHub repo with README that explains the differences, setup, and roles used.

Overview

This redwoodblog app is a modified-version of the RedwoodJS blog engine tutorial with some added tweaks:

  • TailwindCSS and UI
  • User Profile / Settings
  • Role-based Access Control (RBAC) on Posts
  • User Management via Netlify Identity API (view users)
  • Contact messages get associated with user, if logged in
  • Posts have an optional author and publisher set by currentUser
  • Uses Netlify Identity Trigger Serverless function calls to assign roles when signing up
  • Lists users (admin only) via Netlify Identity API

Note: This app does not store any User information in a database, but rather integrates with Netlify Identity.

You can access a demo at https://redwoodblog-with-identity.netlify.app/.

Also, all Post data will be deleted and reseeded via Netlify webhook nightly and repeater.dev.

Roles

There redwoodblog defines the following roles:

  • Admin
  • Author
  • Editor
  • Publisher

Depending on the user’s role(s), their access to Posts will differ.

For example, Edwina Editor can edit and view, but not add or delete.

See README for all details.

As in the current tutorial, everyone (even those not authenticated) can view posts and submit Contact messages.

Users

Admin users – which no one can get will see a list of users. This comes from the Netlify Identity API. No user record is stored.

How to try this out?

You can signup with a certain username in your email … and assuming you have Gmail, you can append a plus (“+”) sign and any combination of words or numbers after your email address.

On signup, we will automatically assign you roles based on your email via the “Trigger serverless functions on Identity events” feature.

If your email contains:

  • +author as in example+author-example@gmail.com, you will be assigned the author role

  • +editor as in example+editor-example@gmail.com, you will be assigned the editor role

  • +publisher as in example+publisher-example@gmail.com, you will be assigned the publisher role

See: functions/identity-signup.js function for implementation details.

Feedback

Let me know what you think.

8 Likes

@dthyresson this is just amazing! My only concern is making sure this doesn’t get lost in the Forums. Let’s talk soon about how to add this as a doc/cookbook to RWJS.com

Really above and beyond work on this. Excited for others in the community to make great use of it :rocket:

1 Like

I did add a PR to the “awesome-redwood” repo with it since it is more of an example vs tutorial.

2 Likes

Thanks for sharing this example, it has been really helpful for me to go through and understand how the auth and user handling should work, as I’ve not built this kind of fullstack app before.

However, I’ve noticed that the example as-is does not seem to work correctly. For example, in the demo site, I signed up with a gmail address <my_addr>+author@gmail.com. I then could see a Settings page, but it didn’t show any roles. I also couldn’t do anything like add posts.

The main issue concerning me right now is listing users. After looking through a lot of the code and testing it in my own project, it doesn’t seem like the users service is accessing a valid endpoint. I’m not sure how you’re supposed to get a list of users from the GoTrue API; it doesn’t look like that is exposed. I’m wondering if it’s necessary to tie each Netlify Identity user to a user db record.

Hi @jevogel and thanks for trying out the RBAC. example.

I see that you signed up with two accounts one with a +author and oner with +admin in the emails.

The admin does not have any roles associated with it – I did not implement granting admin access. I did not explicitly say that in the write up – but you can see here:

I do see your “author” account.

There may be a slight race condition if when signing up you gain access before the role gets sent.

Screen Shot 2020-09-02 at 00.37.56

Could you logout and login again with the author user you setup?

Be certain to logout and confirm when you login that the new user has logged in.

Ah - I have a bug in

I forgot to use the ENV for the site name:

  const adminToken = context.clientContext?.identity?.token

  if (adminToken) {
    const { body } = await got.get(
      'https://redwoodblog-with-identity.netlify.app/.netlify/identity/admin/users',
      {
        responseType: 'json',
        headers: {
          authorization: `Bearer ${adminToken}`,
        },
      }
    )

    return body['users']

Please replace the redwoodblog-with-identity in

https://redwoodblog-with-identity.netlify.app/.netlify/identity/admin/users

with your site.

You’ll need to manually assign your user the admin role in the Netlify Identity UI.

export const users = async () => {
  requireAuth({ role: 'admin' })

  const adminToken = context.clientContext?.identity?.token

This adminToken is what Netlify calls a “short-lived” token obtained from the logged in user to access admin-type permissions on GoTrue.

The user object is present if the function request has an Authorization: Bearer <token> header with a valid JWT from the Identity instance. In this case the object will contain the decoded claims.

The identity object has url and token attributes. The URL is the endpoint for the underlying GoTrue API powering the Identity service. The token attribute is a short-lived admin token that can be used to make requests as an admin to the GoTrue API.

See: https://docs.netlify.com/functions/functions-and-identity/#access-identity-info-via-clientcontext

So it can make a call here to fetch all users:

if (adminToken) {
    const { body } = await got.get(
      `https://${process.env.SITE_NAME}.netlify.app/.netlify/identity/admin/users`,
      {
        responseType: 'json',
        headers: {
          authorization: `Bearer ${adminToken}`,
        },
      }
    )

    return body['users']

Important: You cannot test this locally in dev. I did not make that clear I now see. The function has to be running on Netlify’s infrastructure to access that adminToken.

I just redeployed and committed the change to use the ENV.

Screen Shot 2020-09-02 at 00.53.51

Actually, now that I re-read the docs:

identity object has url

I bet I could use that instead of the ENV. Let’s try that …

Yup that worked:

export const users = async () => {
  requireAuth({ role: 'admin' })

  const adminToken = context.clientContext?.identity?.token
  const identityEndpoint = context.clientContext?.identity?.url

  if (adminToken && identityEndpoint) {
    const { body } = await got.get(`${identityEndpoint}/admin/users`, {
      responseType: 'json',
      headers: {
        authorization: `Bearer ${adminToken}`,
      },
    })

    return body['users']

So now you do not need the SITE_NAME here, but you do for the user link in the User card.

Let’s see if we can’t get this working for you. Let me know how it goes.

Thanks again for trying the demo out!

2 Likes

Ah yes, the demo does work correctly for me now, I guess it just took a bit to update.

Thanks for the explanation, that makes more sense now. I had already fixed the URL issue, but I was also not seeing any context.clientContext object in development. Is that only available when deployed? I saw in the userMetadata service that you were using context[1].token, which I was able to see, but that token didn’t give access to the users endpoint.

I am trying to create “posts” that are owned by a “creator” user, and I want to be able to display that users’s name, email, and other metadata when the post is loaded. A user shouldn’t have to be admin to see who wrote a post. If the only way to access user metadata is as admin, then I guess it would be necessary to link to a record in my database instead.

Anyway, I’ll give your fix a try soon and let you know how it goes.

Correct. Netlify’s infrastructure will create that for you. It does make it a little tricky to do development to be sure.

Have you thought about fetching that creator user’s info using the get user with the admin token access in the service on Post save and storing that with the Post?

It could be a PostHistory record that belongs to the Post. Like an audit trail and on each save the info is stored. When viewing the Post in includes the PostHistories sorts and shows the first most recent one.

1 Like

The Redwood Tutorial Blog w/ Netlify Identity and RBAC Example has been updated:

  • Uses RWJS v0.18.0 with Netlify Identity Sign Up support to present sign up tab when desired
  • Has Get Started (aka Sign Up) on updated About Page
  • The About Page now has a FAQ that explains roles and how sign up works to assign to a role
1 Like

New RBAC Cookbook by @dthyresson is live on the website. It’s just :star_struck:

:new: Quick Update!

The RBAC Blog app has been :crossed_fingers: upgraded to v0.20 to include the changes necessary for the new
:waning_gibbous_moon: Apollo Client v3 :brain: Cache Behavior for GraphQL :alien: Mutations.

1 Like