Using Permissions and Token Verification to Secure Your Site

Article author
Josh Lopez
  • Updated

If you have access to a server or a serverless environment, you can use data fetching libraries like axios to make authenticated requests to your backend. You can use axios in Webflow (with custom code) or in a standard JavaScript environment, like React or Vue.

On your server, you can leverage the Memberstack admin API to access admin specific data from your Memberstack account. Use this API to programmatically create members, retrieve all members, and verify a member’s access tokens.

By combining data-fetching on the client side with our admin API on the server side,

The Member Object

In the Memberstack 2.0 API , members are represented as JSON “objects” with relevant data attached.

The Member object looks like this:

{
  data: {
    id: "mem...",
    auth: {
      email: "john@doe.com"
    }
    customFields: {
      country: "Germany"
    },
    metaData: {
      avatar: "photo.png"
    },
    permissions: ["can:view-members", "is:admin"],
    planConnections: [
      {
        id: "con_...",
        status: "ACTIVE"
        planId: "pln_...",
        type: "FREE",
        payment: null
      },
    ]
  }
}

Notice the permissions field. It's an array of string values based on the permissions that were set on a member manually, or they signed up for a plan. You can do ALOT with this permissions array when it comes to gating content and authorizing requests for secure data.

Using Permissions to Gate Content In React

Here's how we use arrays of permissions to gate content in our @memberstack/react package:

<MemberstackProtected
  allow={{
		plans=[...]
    permissions: ["is:admin"]
  }}
>
  <AdminOnlyArea />
</MemberstackProtected>

This component will evaluate an array of permissions supplied in the allow property and render the content inside of the wrapper only if the member has those permissions.

Using Permissions AND Access Tokens to Secure Backend Requests

If you have access to a backend environment (server, serverless lambdas, edge workers), you can use our memberstack admin package to verify a member’s access token AND control what data get’s returned based on their permissions. Verifying tokens on the server is an industry standard approach to authentication and considered a best practice for authorizing requests.

This example below first verifies the member’s access token and returns a 401 Unauthorized error back to the client if the token is invalid or the member is not found.

const memberstackAdmin = require("@memberstack/admin");

const memberstack = memberstackAdmin.init(process.env.MEMBERSTACK_SECRET_KEY);
const REQUIRED_PERMISSIONS = ["can:view-members"];

async function handler(req, res)  {
  let member
  try {
		// get access token from the _ms-mid cookie in headers
    const token = getCookie(req.headers.get("cookie"), "_ms-mid");
    // verify the token and extract the member id from the jwt payload
    const { id } = await memberstack.verifyToken({ token })
    // use the member id to retrieve the member's info from memberstack
    let { data } = await memberstack.members.retrieve({ id })
    member = data
  } catch (error) {
    console.log(error)
		 // catch errors from above and return a 401 unauthorized error 
    return res.status(401).send("Unauthorized");
  }

....
....

Now here’s is where it gets interesting. You can create your own permission handlers using various array methods*:*

// These are the permissions we require to authorize the request and send back data
const REQUIRED_PERMISSIONS = ['can:view-members', 'is:admin'];

// This function returns true if the member has ALL permissions listed in
// REQUIRED_PERMISSIONS. 
// Note that the member can have additional permissions not listed,
// but they must have ALL of the required ones at least:
const hasRequiredPermissions = (permissions) =>
  REQUIRED_PERMISSIONS.every((permission) => permissions.includes(permission));

// this function returns true if the member has the exact combination of 
// permissions listed in REQUIRED_PERMISSIONS. No more or no less.
const hasExactPermissions = (permissions) =>
permissions.length === REQUIRED_PERMISSIONS.length &&
permissions.every(permission => REQUIRED_PERMISSIONS.includes(permission));

In the example below, we have a list of required permissions, and we’re passing in “member” permissions to the function handlers we just created.

Let’s see these functions in action:

const REQUIRED_PERMISSIONS = ['can:view-members', 'is:admin'];
...
....

console.log(hasRequiredPermissions(['can:view-members', 'is:member']));
// returns false (missing 'is:admin')
console.log(hasExactPermissions(['can:view-members', 'is:admin']));
// return true (has exact combination)

The great thing about the permission handlers is that they return Boolean values (true or false), which help us to conditionally route the flow of logic.

Now, let’s add this to the rest of our code.

...
const REQUIRED_PERMISSIONS = ['can:view-members', "is:admin"];
let permissions = member?.permissions

// if member does not have ALL of required permissions,return 401
  if (!hasExactPermissions(permissions))
    return res.status(401).json({ error: "Unauthorized" });
  
  // if member has required permissions,
  //  return a list of all members in the memberstack account
  const members = await memberstack.members.list()
  return res.status(200).json({ members });
}

The example above demonstrates that this member has admin permissions and they are authorized to retrieve a list of all members. They are required to have the exact combination of permissions to retrieve data from this endpoint.

If you wanted to to loosen restrictions, you could swap out the permission handlers:

const REQUIRED_PERMISSIONS = ['can:read-articles', "is:editor"];
let permissions = member?.permissions

// if member has at least some of required permissions, return data
  if (hasRequiredPermissions(permissions))
		const articles = await myCMS.getAllPosts()
    return res.status(200).json({ articles });
...
...

Choosing The Right Approach

As you can see, there’s a variety of ways you can manage access, gate content and secure your data with Memberstack.

While the React approach requires a React development environment, you can create Memberstack-powered React widgets, bundle them and embed them in your Webflow site.

Was this article helpful?

Comments

0 comments

Please sign in to leave a comment.