I came up with a hacky workaround for this issue. If you request a non-standard claim (so one not on this page), then auth0 will filter the returned API scopes to only the requested ones. So you can request a scope that doesn’t exist, and Auth0 will filter out all of the API scopes.
So, in my action I first check if the request is a password grant request, and if it is, I check to make sure that it includes a “password_grant” scope. This isn’t a real scope, but including it makes auth0 only include the requested scopes, and not every scope. Since I can read the requested scopes in the event, I can remove them with api.accessToken.removeScope()
. Here is all the code for the action:
const resourceOwnerProtocols = [
"oauth2-password",
"oauth2-resource-owner",
"oauth2-resource-owner-jwt-bearer",
];
const resourceOwnerRequiredScope = "password_grant";
const oidcStandardClaims = new Set([
"openid",
"sub",
"name",
"given_name",
"family_name",
"middle_name",
"nickname",
"preferred_username",
"profile",
"picture",
"website",
"email",
"email_verified",
"gender",
"birthdate",
"zoneinfo",
"locale",
"phone_number",
"phone_number_verified",
"address",
"updated_at",
]);
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
let scopes;
if (resourceOwnerProtocols.includes(event.transaction?.protocol ?? "")) {
// Requested scopes will be in the body
scopes = (event.request.body.scope ?? "").replace(/\s+/g, " ").split(" ");
if (!scopes.includes(resourceOwnerRequiredScope)) {
api.access.deny(
`Password grant requests must include the '${resourceOwnerRequiredScope}' scope.`
);
return;
}
} else if (Array.isArray(event.transaction?.requested_scopes)) {
scopes = event.transaction?.requested_scopes;
} else {
api.access.deny("Unable to read requested scopes");
return;
}
// Remove any scopes which are not OIDC standard claims
for (const scope of scopes) {
if (!oidcStandardClaims.has(scope)) {
api.accessToken.removeScope(scope);
}
}
// Add scopes based on user permissions or whatever else
api.accessToken.addScope("example:scope");
};
Obviously it’s a bit of a hack, but it does work.