TL;DR
- Can I verify Memberstack JWTs on my own server without calling the Memberstack API? Yes. Memberstack tokens are signed using the RS256 asymmetric algorithm.
- The JWKS Endpoint: You can fetch Memberstack's public keys from our JSON Web Key Set (JWKS) endpoint: https://auth.memberstack.com/jwks. (You can view the full OpenID Connect configuration at https://auth.memberstack.com/.well-known/openid-configuration).
- Why this is awesome: Because verification uses asymmetric cryptography, you can verify your members' identities locally in under 1 millisecond using standard JWT libraries in any language (Node.js, Python, Go, Ruby, etc.) without hitting Memberstack's API or rate limits.
- How to get the token: Configure Memberstack to use cookies (useCookies: true), which places the signed JWT in the _ms-mid cookie, or retrieve it client-side using window.$memberstackDom.getMemberCookie()and pass it in the Authorization: Bearer <token> header.
Part 1) Verify The Member's Identify
1. Understanding Memberstack JWTs
When a member logs into your site, Memberstack issues a JSON Web Token (JWT) containing their unique member ID, metadata, and plan permissions.
- Signing Algorithm: RS256 (RSA Signature with SHA-256). Memberstack signs the token using our private key, and you verify it using our public keys retrieved from our JWKS endpoint.
- Issuer (iss): https://api.memberstack.com
- Audience (aud): Your Memberstack App ID (starts with app_...)..
2. Retrieving and Passing the Token
To verify a token on your backend, you must first retrieve it from the frontend. There are two standard ways to do this:
Method A: The Cookie Pattern (Recommended for Server-Side Rendering)
You can configure Memberstack to automatically store the JWT in a secure cookie. This ensures the token is automatically sent with every single HTTP request to your backend.
- When initializing Memberstack on your frontend, set useCookies: true in your configuration:
import memberstackDOM from '@memberstack/dom';
const memberstack = memberstackDOM.init({
publicKey: "YOUR_PUBLIC_KEY",
useCookies: true,
setCookieOnRootDomain: true
});- On your backend, extract the token from the request headers by looking for the _ms-mid cookie. (Note: The _ms-mid is the cookie containing the JWT.
Method B: The Header Pattern (Recommended for Single Page Apps / REST APIs)
If you prefer to make AJAX/fetch requests to your API, you can grab the token in JavaScript and append it as a Bearer token.
- Retrieve the token in your frontend code:
// Retrieve the JWT from the cookie client-side const token = window.$memberstackDom.getMemberCookie();
- Send the token in your HTTP headers:
fetch('https://api.yourdomain.com/data', {
headers: {
'Authorization': `Bearer ${token}`
}
});3. Local Verification Code Samples (No SDK Required)
To verify the token locally, your server needs to:
- Decode the token's header to read the Key ID (kid).
- Fetch the matching public key from https://auth.memberstack.com/jwks'.
- Verify the signature, issuer (https://api.memberstack.com), and expiration date.
Here is how to implement this in the most popular languages:
đ˘ Node.js (Vanilla JWT Verification)
If you do not want to use @memberstack/admin, you can use the standard jsonwebtoken and jwks-rsa packages.
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
// 1. Configure the JWKS client to fetch and cache Memberstack's public keys
const client = jwksClient({
jwksUri: 'https://auth.memberstack.com/jwks',
cache: true, // Cache public keys in memory
cacheMaxEntries: 5, // Maximum number of keys to cache
cacheMaxAge: 86400000 // Cache keys for 24 hours (in milliseconds)
});
// Helper function to retrieve the correct signing key
function getKey(header, callback) {
client.getSigningKey(header.kid, function(err, key) {
if (err) {
return callback(err);
}
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
// 2. Verify the member JWT
const token = "YOUR_MEMBER_JWT_HERE"; // Retrieved from Authorization header or _ms-mid cookie
const options = {
issuer: 'https://api.memberstack.com',
audience: 'app_your_memberstack_app_id', // Replace with your App ID
algorithms: ['RS256']
};
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
console.error('JWT Verification Failed:', err.message);
// Handle unauthorized request
return;
}
// Token is valid!
console.log('Verified Member Payload:', decoded);
const memberId = decoded.sub; // The "sub" claim contains the unique Member ID
});
đ Python (using PyJWT)
Install dependencies: pip install PyJWT cryptography requests
import jwt
# 1. Configure the JWK Client to fetch and cache Memberstack's public keys
jwks_url = "https://auth.memberstack.com/jwks"
jwks_client = jwt.PyJWKClient(jwks_url)
token = "YOUR_MEMBER_JWT_HERE" # Retrieved from Authorization header or _ms-mid cookie
app_id = "app_your_memberstack_app_id" # Replace with your App ID
try:
# 2. Retrieve the signing key corresponding to the token's 'kid' header
signing_key = jwks_client.get_signing_key_from_jwt(token)
# 3. Verify the token signature, issuer, and audience
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=app_id,
issuer="https://api.memberstack.com"
)
# Token is valid!
print("Verified Member Payload:", payload)
member_id = payload.get("sub") # The "sub" claim contains the unique Member ID
except jwt.exceptions.ExpiredSignatureError:
print("JWT Verification Failed: Token has expired")
except jwt.exceptions.InvalidTokenError as e:
print(f"JWT Verification Failed: {str(e)}")đľ Go (using golang-jwt/jwt and lestrrat-go/jwx)
Install dependencies: go get github.com/golang-jwt/jwt/v5 and go get github.com/lestrrat-go/jwx/v2/jwk
package main
import (
"context"
"fmt"
"log"
"github.com/golang-jwt/jwt/v5"
"github.com/lestrrat-go/jwx/v2/jwk"
)
func main() {
tokenString := "YOUR_MEMBER_JWT_HERE" // Retrieved from Authorization header or _ms-mid cookie
appID := "app_your_memberstack_app_id" // Replace with your App ID
jwksURL := "https://auth.memberstack.com/jwks"
ctx := context.Background()
// 1. Fetch the JWK Set from Memberstack
// (In production, cache this set in-memory so you don't hit the network on every request)
jwkSet, err := jwk.Fetch(ctx, jwksURL)
if err != nil {
log.Fatalf("Failed to fetch JWKS: %v", err)
}
// 2. Parse and verify the token signature, issuer, and audience
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Confirm the signing algorithm is RSA
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// Extract the Key ID (kid) from the JWT header
kid, ok := token.Header["kid"].(string)
if !ok {
return nil, fmt.Errorf("missing 'kid' header")
}
// Find the matching public key in the JWK Set
key, found := jwkSet.LookupKeyID(kid)
if !found {
return nil, fmt.Errorf("key %s not found in JWKS", kid)
}
// Extract the raw public key
var rawKey interface{}
if err := key.Raw(&rawKey); err != nil {
return nil, fmt.Errorf("failed to extract raw public key: %w", err)
}
return rawKey, nil
}, jwt.WithIssuer("https://api.memberstack.com"), jwt.WithAudience(appID))
if err != nil {
fmt.Printf("JWT Verification Failed: %v\n", err)
return
}
// 3. Extract claims from the verified token
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
// Token is valid!
fmt.Println("Verified Member Payload:", claims)
memberID := claims["sub"].(string) // The "sub" claim contains the unique Member ID
fmt.Printf("Success! Member ID: %s\n", memberID)
}
}đ´ Ruby (using jwt)
Install dependency: gem install jwt
require 'jwt'
require 'net/http'
require 'json'
# 1. Fetch Memberstack's JWK Set
# (In production, cache this JWKS response in-memory to avoid fetching it on every request)
jwks_url = 'https://auth.memberstack.com/jwks'
jwks_response = Net::HTTP.get(URI(jwks_url))
jwks_keys = JSON.parse(jwks_response)
token = 'YOUR_MEMBER_JWT_HERE' # Retrieved from Authorization header or _ms-mid cookie
app_id = 'app_your_memberstack_app_id' # Replace with your App ID
# 2. Configure JWT verification options
options = {
algorithm: 'RS256',
aud: app_id,
verify_aud: true,
iss: 'https://api.memberstack.com',
verify_iss: true,
jwks: jwks_keys
}
begin
# 3. Decode and verify the token signature and claims
decoded_token = JWT.decode(token, nil, true, options)
# Token is valid!
payload = decoded_token[0]
puts "Verified Member Payload: #{payload}"
member_id = payload['sub'] # The "sub" claim contains the unique Member ID
rescue JWT::ExpiredSignature
puts "JWT Verification Failed: Token has expired"
rescue JWT::DecodeError => e
puts "JWT Verification Failed: #{e.message}"
end4) Alternative: Raw HTTP API Verification (No SDK)
If you do not want to parse the JWT cryptography locally and prefer to offload the verification to Memberstack via standard HTTP POST, you can call our raw token verification endpoint directly.
- Endpoint: POST https://admin.memberstack.com/members/verify-token
- Headers:
- X-API-KEY: YOUR_SECRET_KEY (Your sk_... or sk_sb_... key)
- Content-Type: application/json
- JSON Payload:
{
"token": "YOUR_MEMBER_JWT_HERE"
}5) The Token Payload Structure
Once verified, the decoded JWT payload will contain the following claims:
{
"data": {
"id": "mem_abc123", // Member ID
"type": "member", // Token type
"iat": 1681414876, // Issued at timestamp
"exp": 1682624476, // Expiration timestamp
"aud": "app_xyz456", // Audience (app ID)
"iss": "https://api.memberstack.com" // Issuer
}
}Part 2) Authorize Based on Permissions
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 Svelte 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,
1) 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 is 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.
2) 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 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 are 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 });
...
...
3) Choosing The Right Approach
As you can see, there are a variety of ways you can manage access, gate content and secure your data with Memberstack.
âFrequently Asked Questions (FAQ)
Q: Should I call the JWKS endpoint on every request?
No. Your JWT library should cache the JWKS keys in memory. Memberstack's signing keys change very infrequently. Caching keys ensures your server can verify tokens instantly (under 1ms) without making an external HTTP request to Memberstack on every API call.
Q: How long are Memberstack member JWTs valid?
Memberstack tokens have a default lifespan of 14 days. Your backend code should reject any tokens where exp is in the past.
Q: Can I use this strategy with Serverless Functions (AWS Lambda, Vercel, Supabase)?
Yes! This local verification is ideal for serverless environments because it does not require database lookups or external API calls, keeping your cold starts fast and execution times extremely low.
Comments
7 comments
how can i check the user token with the rest api? i have problem sending the token in the doc is not clear how to set the params token, I'm receiving error 400 invalid tokenÂ
also I'm not sure i get the right token from the front-end, I'm using xano thanks
Josh Lopez Do you have any recommendations for Arnau Ros?
I'm afraid this is over my head.Â
Arnau Ros After doing a bit more research it seems JWT verification can only be done with the Node.js version of the Admin Package. It's not possible to verify JWT with the REST version right now.Â
What could be the equivalent of that when we are using webflow?
Hi Eric, I don't think there is an equivalent when using Webflow. We handle all of the verification for you đ
Hey, regarding this:
Is there still no possibility to verify the JWT with REST?
Deividas Kovger
https://developers.memberstack.com/admin-rest-api/verification
Please sign in to leave a comment.