Roles RBAC Integration
Request the OAuth roles scope, persist trusted platform roles, and map them to service-local permissions.
SDK snapshot 21344f2c
Use the OAuth roles scope when a service needs trusted TCM platform roles for local RBAC.
Contract
rolesexposes platform roles from Portal userplatformRoles.rolesdoes not expose tenant, community, or self-service role-management documents.- Returned roles are simple strings such as
adminandtester. - Permissions are not sent through OAuth. Each service maps roles to local code-level permissions.
- Unknown role names must be ignored by the service.
When the scope is granted, /oauth/userinfo includes:
{
"sub": "user-id",
"userName": "alice",
"roles": ["admin"]
}If the user has no platform roles, roles may be omitted or returned as an empty array.
OAuth Client Setup
Enable the roles scope on the OAuth client in the Developers portal. Keep profile enabled because it is required for normal user identity.
Recommended scope for platform RBAC services:
scope: "profile roles"If the service also needs email or other fields, request the narrowest allowed scope set:
scope: "profile email roles"The requested scopes must already be included in the client's allowed scopes, otherwise Portal rejects the authorization request.
Exchange Route
Persist userInfo.roles into the app-local session during the server exchange.
const route = createTcmOAuthExchangeRoute({
oauth: {
apiBaseUrl: process.env.TCM_OAUTH_API_URL!,
clientId: process.env.NEXT_PUBLIC_TCM_CLIENT_ID!,
clientSecret: process.env.TCM_OAUTH_CLIENT_SECRET!,
callbackPath: "/auth/tcm/callback",
},
async onResolvedUser({ userInfo, tokenSet }) {
const user = await upsertUserFromTcm(userInfo);
return {
body: { userId: user.id },
session: {
sub: user.id,
roles: Array.isArray(userInfo.roles) ? userInfo.roles : [],
accessToken: tokenSet.accessToken,
tokenType: tokenSet.tokenType || "Bearer",
},
};
},
applySession(response, session) {
sessionAdapter.apply(response, signSession(session));
},
});Do not depend on frontend state alone. Privileged API routes must read the trusted server session or refetch /oauth/userinfo server-side.
Service RBAC Helper
Define role-to-permission mapping in the service codebase.
export const ROLES = {
ADMIN: "admin",
TESTER: "tester",
} as const;
export const PERMISSIONS = {
MANAGE_CAMPAIGNS: "manage_campaigns",
REVIEW_PAYOUTS: "review_payouts",
ACCESS_TEST_GAME: "access_test_game",
} as const;
export const ROLE_PERMISSIONS = {
[ROLES.ADMIN]: [
PERMISSIONS.MANAGE_CAMPAIGNS,
PERMISSIONS.REVIEW_PAYOUTS,
PERMISSIONS.ACCESS_TEST_GAME,
],
[ROLES.TESTER]: [
PERMISSIONS.ACCESS_TEST_GAME,
],
} as const;Normalize roles before checking permissions:
function getEffectiveRoles(source: { roles?: unknown } = {}) {
const roles = Array.isArray(source.roles) ? source.roles : [];
const allowed = new Set(["admin", "tester"]);
return Array.from(
new Set(
roles
.map((role) => String(role || "").trim().toLowerCase())
.filter((role) => allowed.has(role)),
),
);
}
function hasPermission(source: { roles?: unknown }, permission: string) {
return getEffectiveRoles(source).some((role) =>
ROLE_PERMISSIONS[role as keyof typeof ROLE_PERMISSIONS]?.includes(permission as never),
);
}API Guard
Apply permission checks on every privileged API route.
export function requirePermission(request: Request, permission: string) {
const session = readSignedSession(request);
if (!session?.sub) {
return {
error: Response.json({ message: "Unauthorized" }, { status: 401 }),
};
}
if (!hasPermission(session, permission)) {
return {
error: Response.json({ message: "Forbidden" }, { status: 403 }),
};
}
return { session };
}Use frontend checks only to improve UX:
- hide admin links for non-admin users
- show a signed-in but forbidden message for authenticated users without the role
- never redirect an authenticated-but-forbidden user to login
Security Rules
- Do not authorize platform admin routes from usernames, email allowlists, tenant roles, or community roles.
- Do not mix tenant role-management documents into OAuth platform RBAC.
- Do not persist permissions in Portal for this model; persist only platform roles in Portal and keep service permissions in code.
- Do not grant access from
rolesunless the token was issued by TCM OAuth and the service requested therolesscope. - Re-check roles on server routes even when the UI already hides the action.