Skip to main content

Technical Advanced - frequently asked questions

How do we make sure only to trust Authress JWTs and not every valid JWT out there?โ€‹

If you are using an Authress SDK then you likely will have code that looks similar to this:

const authressClient = new AuthressClient({ authressApiUrl: AuthressCustomDomain });
const userIdentity = await authressClient.verifyToken(userToken);

The important part here is that the authressApiUrl and the userToken are both passed into the Token Verifier. That means we can compare the two. Specifically, we check that the issuer found in the token matches the authressApiUrl. This check can be found in the SDK here: Authress Javascript SDK.

If I don't use an Authress Service Client, then how does the Authress SDK verify the user permissions?โ€‹

Your users can always check their own permissions. In order for the Authress SDK to function, you'll need to set or pass in either a User JWT or a service client access token.

A common configuration is:

const authressClient = new AuthressClient({ authressApiUrl: AuthressCustomDomain });
const userIdentity = await authressClient.verifyToken(userToken);

await authressClient.userPermissions.authorizeUser(userIdentity.sub, 'accounts/account_001/objects/obj_a', 'objects:read');

If you pass the user's JWT to the authressClient and then you also pass that user's user ID, Authress says "Okay you are allowed to do that", and then returns the results. If the user in the token matches the permissions check or if the access token used belongs to a client that has permissions, the check will return a 200 or a 404 depending on if the user has access. If the token does not have permission to check other user's permissions, then the result will be a 403.

How does Authress know that this request is for my account vs another authress customer that just happens to use the same resource and verb values?โ€‹

This is essentially "how does multitenancy work in Authress". Multitenancy is actually keyed off two factors, the domain used to call Authress and the JWT issue that is passed to us. Every Authress account has its own subdomain, and also its own custom domain CNAME. The subdomain will be acc-accountId.api.authress.io, and a custom CNAME might be auth.yourdomain.com or auth.yourdomain.com, which points at the acc-accountId.api.authress.io.

In almost every situation both of these are unique to the Authress account. The Authress tenant that is selected to be the source of truth is based on the custom domain. We check to see if in that specific Authress tenant, the role/permission & resource for that user specified is present. If that user's JWT access token came from a different account, we actually block those requests, and additionally we log that it happened. It isn't the case today we allow cross-account authorization, and because of that, if we detect too many of these we escalate which can result in account termination of the source of the JWT. This is only partly automated due to the severity of the remediation.

This means we always know which account a token and a request is associated with.

Why are users able to log in without signing up first?โ€‹

The core of the questions comes down to don't users have to first click a "Sign up" button during Authress Login in order to first create an account. This is because "Sign up" is a legacy antipattern, it exists as only a user ritual. Some users attempt to do this because of past traumas in their lives, so most importantly, it is an unnecessary ritual at that. The existence of the Sign up link in the Authress provided Login Box is purely UX aesthetic for users to perform a ritual they may believe is important.

Let's take a look at what is important:

  • Users will sometimes forget that they have created an account
  • Users might not know if they have created an account
  • What should we do with users that have created an account and attempt to Sign up again

Authress is built some simple UX rules, the most basic one is "If know what the user wanted to do, then do it". So we made it that users can always login, irrespective of what button they push, they'll never get a "I'm sorry, you can't sign up you already have an account" or likewise "I'm sorry, you can't log in, you don't have an account". If we know whether they already have an account, then they get to move to the next stage.

As a counterargument, we can say:

We want to do something different when the user clicks sign-ups.

The problem is, what do you want to do for a user that clicks Sign up, but already has an account. Because only Authress knows this information and the user hasn't logged in yet, you need to actually force the user to log in, in order to answer that question.

Therefore, when we see these user stories, it makes the most sense to always log the user in. There is never a reason not to let them do that. However, that doesn't necessarily get us off the hook though, we might have other requirements for them. An example might be a Wait List. That is users cannot use the application or create an account until they are approved by someone on your team.

To solve this, when a user logs in through Authress and is redirected back to your Application, at this moment, validate whether or not they have created an account already. If they have access to your app or if they need special approval, you can fetch this information and display the right screen to a user.

You can do this with data you have in your database, or because you are using Authress already, you also can do this with one simple call from your UI to Authress.

Using the user's JWT access token received from Authress as a result of logging in, you check to see if they already have an account created. Assuming every time a user is approved to create an account, you manually create the account, and then give them access:

(Application UI) Check if user has access to any accounts.
import { UnauthorizedError, AuthressClient } from '@authress/sdk';
const authressClient = new AuthressClient({ authressApiUrl: AuthressCustomDomain }, userToken);
const userIdentity = await authressClient.verifyToken(userToken);

const response = await authressClient.userPermissions.getUserResources(userIdentity.sub, 'accounts/*', 'accounts:read');
if (response.data.resources.length) {
// User already has an account
router.push({ name: 'Home' });
return;
}

try {
await authressClient.userPermissions.authorizeUser(userIdentity.sub, 'accounts/*', 'accounts:create');
// User has access to create an account
// Redirect the user to account creation
router.push({ name: 'AccountCreation' });
return;
} catch (error) {
if (error.code === 'UnauthorizedError') {
// User does not have access to create an account
// Redirect the user to an "Sign up screen"
router.push({ name: 'SignUp' });
return;
}

throw error;
}

Or a similar check can be done using your own systems. But what we never need to do is try to figure out if the user has an account, before they have logged in, as this creates a simpler Developer Experience and a more reliable end user experience.