AWS Builder Center

Branch-Scoped Roles in Amazon Cognito Without Group Sprawl

Cognito groups are flat, so multi-location apps struggle to express "Admin at Branch A, Staff at Branch B." This article shows how to handle it with one custom attribute and a Pre Token Generation Lambda.

The problem

In a multi-branch app (dorms, clinics, franchises), a role only makes sense per branch. Cognito user pool groups are flat, so the obvious fix is one group per combination: branchA-admin, branchA-staff, branchB-admin... That grows with every new branch and turns into a maintenance problem quickly.

The approach

  1. Store each user's branch roles in a single custom attribute.
  2. Use a Pre Token Generation Lambda to put them into the access token.
  3. Check the claim in your backend.

1. Add a custom attribute

Create custom:branch_roles in your user pool and store a JSON string:
1
{"branch-a": "admin", "branch-b": "staff"}
Important: remove write permission for this attribute from your app client. Otherwise users could edit their own roles. Only your admin backend (via AdminUpdateUserAttributes) should set it.

2. Pre Token Generation Lambda (event version V2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export const handler = async (event) => {
let branchRoles = {};
try {
branchRoles = JSON.parse(event.request.userAttributes["custom:branch_roles"] || "{}");
} catch {
branchRoles = {};
}

event.response = {
claimsAndScopeOverrideDetails: {
accessTokenGeneration: {
claimsToAddOrOverride: { branch_roles: branchRoles }
}
}
};
return event;
};
Note: customizing the access token requires the V2 trigger event, which is available on the Essentials or Plus feature plan.

3. Enforce it in your API

1
2
3
4
5
6
7
8
9
10
11
12
import { CognitoJwtVerifier } from "aws-jwt-verify";

const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.USER_POOL_ID,
tokenUse: "access",
clientId: process.env.CLIENT_ID,
});

const payload = await verifier.verify(token);
if (payload.branch_roles?.[branchId] !== "admin") {
return res.status(403).json({ error: "Forbidden" });
}

Trade-offs

  • Role changes take effect only after the user's token refreshes.
  • You now maintain a Lambda for something that arguably belongs in Cognito itself.
That second point is why I submitted a Wishlist request **** for native scoped groups in Cognito. If you've hit the same problem, give it a vote.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article