Ever wonder why a fitness app asks to read your heart rate but doesn't need your bank login? That's oauth scopes doing the heavy lifting behind the scenes.
Basically, scopes are strings that act like specific keys for rooms in a house rather than a master key for the whole building. (The benefits of using a scope, including Mortice Lock I.D – YouTube) According to OAuth 2.0 Scopes, this mechanism limits an application's access to a user's account so they only get what they actually need.
The authorization server (where you log in) defines what these strings mean. It's not just for tech companies—think about these examples:
records.read but not billing.write.purchase.history to give you discounts.transactions.read to categorize spending.Before we move on, how do these actually look in the code? The oauth 2.0 standard keeps it dead simple. Scopes are just case-sensitive strings. If an app wants multiple permissions, it sends them as a single string where each scope is separated by a space. Like this: openid profile email. When your api receives this, it just splits that string by the spaces to see a list of what the app is allowed to do. It's not fancy, but it works.
It's all about that "least privilege" vibe. The api won't let the app do anything the user didn't check a box for. Next, let's look at how the user actually sees this in the real world.
So, you’ve kicked off the auth flow. What actually happens on the user's screen? It’s that familiar (and sometimes annoying) popup asking if "App X" can read your emails.
This part is all about the consent screen. According to IBM Documentation, this page is a template the auth server fills with specific details so the user knows what they’re signing up for.
crm.leads.read, they see "View your sales leads."location and purchase_history, a user might deny location but still want the rewards.Think about a healthcare app. If it asks for patient.records.write but the user only trusts it to read, they might trim that permission right there.
It's a delicate balance. If the language is too technical, users get scared and bounce. If it's too vague, you've got a security nightmare. (Is THIS a real phobia? (fear of errors/glitches/technical difficulties …) Next up, let's look at how we actually design these things for big companies.
Building for the enterprise isn't just about adding a "Login with Google" button. When you're dealing with big players in healthcare or finance, they expect you to play nice with their existing directory sync and complex saml setups.
If you're trying to scale, you can't spend months building custom connectors for every client's legacy system. That's where an api-first platform like SSOJet comes in handy—it handles the messy stuff like directory sync, oidc, and magic links so you can focus on your actual product.
reports.read). I've seen some real messy scope names in my time. Honestly, keep it simple using a resource.operation pattern. This makes it way easier for third-party devs to understand what they are asking for.
accounts.read or payments.write. It makes it obvious what the app is trying to do.user.name.read, user.email.read, and user.photo.read, you'll just annoy people with consent fatigue. admin scopes because they give away way too much power at once.// Example of checking a scope in your api
if (!token.scopes.includes('transactions.read')) {
return res.status(403).send('You cant see this!');
}
It's all about making things predictable for the devs using your api. Next, let's dive into how to handle these tokens once they're actually issued.
So you've got a shiny access token from the auth server, but now what? Your api actually has to do the heavy lifting to make sure that token isn't just a random string of junk.
First thing, your middleware needs to crack open that jwt. You aren't just checking if the signature is valid (though you definitely should do that first); you're looking for the scope claim. As previously discussed in the oauth.net docs, these are usually just space-separated strings.
scope field from the payload. If it's a jwt, you can decode it using libraries like jose or jsonwebtoken.read and the token has read_everything, a simple includes() might give you a false positive. Always split by spaces into an array first.According to the HighLevel API docs, you might even deal with different token types—like "Agency" vs "Location". This is important because scopes alone don't always tell the whole story. A scope might say you can read.contacts, but the "Location" context tells the api which specific office or branch those contacts belong to. You gotta check both the scope and the resource level to keep data siloed properly.
function checkScopes(requiredScope) {
return (req, res, next) => {
// assume req.user was populated by a previous auth middleware
const scopes = req.user.scope ? req.user.scope.split(' ') : [];
if (scopes.includes(requiredScope)) {
return next();
}
res.status(403).json({ error: "missing_scope", scope: requiredScope });
};
}
Honestly, keep your validation logic as close to the route as possible so it's easy to audit later. Up next, let's wrap things up by looking at the big security risks that can break your system.
Ever wonder why big hacks happen even with oauth? It's usually not the protocol, but how we use it—mostly broad scopes that never expire.
identity.full when they just need a username.email.send and it hallucinates, it might blast your whole contact list with spam.Honestly, use refresh tokens to keep access windows tight. As previously discussed in the oauth.net docs, the server can always grant less than requested to keep things safe. Keep it lean, or risk third-party apps getting too much power over your data.
In the end, oauth scopes are your first line of defense. If you name them well, keep them granular, and always validate them on the api side, you'll be way ahead of most devs. Just remember to review your permissions every once in a while so they don't grow into a mess nobody understands.
*** This is a Security Bloggers Network syndicated blog from SSOJet - Enterprise SSO & Identity Solutions authored by SSOJet - Enterprise SSO & Identity Solutions. Read the original post at: https://ssojet.com/blog/oauth-scopes-consent-secure-api-authorization-guide