Permissions

How scopes work for apps built on the SDK. Registering them, requesting them, checking what was granted, adding more later, and telling a missing permission from a rule.

A permission (the technical word is scope) is a single thing a person allows your app to do, such as reading the feed or publishing a post. Signing in needs none beyond openid profile; a UNET client asks for one permission per kind of call. People see the list before they allow it.

Three places that must agree

  1. The app's registration. In the console, the app has a list of permissions it may ask for. Add only what you use.
  2. Your scopes. The list you pass to new UnetId({ scopes }). openid is always included.
  3. What the person granted. They can allow the request as asked, or leave; the token then carries the scopes that were both granted and registered.
import { UnetId } from "@unet/id-web";

const unet = new UnetId({
  clientId: "unet_xxx",
  redirectUri: `${location.origin}/auth/callback`,
  scopes: ["openid", "profile", "content:read", "notifications:read"],
});

Checking what was granted

unet.hasScope("posts:write"); // true or false
unet.getGrantedScopes(); // ["openid", "profile", "content:read", ...] or null when signed out

Check before you show a feature. If the server did not report the granted scopes, the SDK falls back to the ones you requested.

if (unet.hasScope("posts:write")) {
  showComposer();
}

declare function showComposer(): void;

Adding permissions later

Ask for the least at first and add more when the person reaches a feature that needs it. Add the permission to the app in the console, add it to scopes, then send the person through sign-in again with the permission screen:

await unet.signIn({ prompt: "consent", returnTo: location.pathname });

Existing sessions keep the scopes they were granted; the new ones appear after the person approves.

Missing permission or a rule

Calls to the UNET API can answer 403 for two different reasons, and the SDK separates them:

You getMeaningWhat to do
InsufficientScopeThe session lacks the permission this call needs. scope names it.Ask for it with signIn({ prompt: "consent" }), if your app should have it.
ApiError with code: "not_allowed"A rule applies: the person is blocked, comments are off, the connections are private, you cannot repost your own post.Show a message. A new permission does not help.
ApiError with code: "connections_private"The account hides its follower and following lists.Hide the list.
import { ApiError, InsufficientScope } from "@unet/id-web";

try {
  await api.like(123);
} catch (e) {
  if (e instanceof InsufficientScope) {
    await unet.signIn({ prompt: "consent" }); // e.scope says which permission is missing
  } else if (e instanceof ApiError && e.code === "not_allowed") {
    alert("You cannot like this post.");
  } else {
    throw e;
  }
}

A bare 403 that does not come from UNET (a proxy or a firewall) is an ApiError with code: "forbidden", not InsufficientScope.

Ask for the least

  • Request only what a feature needs. People read the list, and a shorter one is allowed more often.
  • Split features by permission: an app that only reads needs no write permission, and it will never get one.
  • Do not request offline_access unless the person should stay signed in beyond an hour.

What next