[Feature Request]: Log in to specific organization
AFAIK it is not possible to sign in to a specific organization. (unless you use provider: "authkit" which we don't want to use right now, we are using a custom flow.) We are also only using magic auth.
Use case: We have a multi-tenant app, each tenant lives on a subdomain. It would be great to be able to log into tenant1 if we are on tenant1's domain. This usually works okay:
- When the user is only part of tenant1
- When the user is part of tenant1 and tenant2, we can let the log in fail (
organization_selection_required) and then use the subdomain to select the correct organization. - When the user is part of tenant1 and has a pending invite to tenant2 (not necessary, just the example I came across) and tries to log in on tenant2 subdomain, it will log in to tenant1 workos organization without the
organization_selection_requirederror.
How should we go about managing this? Right now, before creating a magic auth, we check the organization members to see if they exist and they have status of active. And if so, send a code, if not don't send code. And we display a message saying "if you are member you will receive code...". We know the organizationId before initiating a log in, so it would be great to be able to just provide this when creating the magic auth.
Is this possible? Would remove a lot of workarounds for us and unlock what WorkOS has to offer
This is what gets returned when the user is part of more than one organization. (Docs)
AFAIK, you need to let the sign in error, and use the response (which contains the organizations that user is a member of) to either show a list of available orgs, or in our case, manually sign the user in that they intended to sign in to (based on domain).
It would be great to get an update on this or at least some guidance from the WorkOS team on how to best manage this.
When this error occurs, you’ll need to display the list of organizations that the user is a member of and authenticate them with the selected organization using the pending authentication token from the error.
It seems like the auth methods are supposed to accept an organizationId parameter — but they currently don’t.
Ideally, it should look like this:
authenticateWithMagicAuth({ code, email, organizationId })
Here's a working example of how to log in to a specific organization:
import {StatusCodes} from 'http-status-codes';
import type {
UserResponse,
AuthenticationResponse,
AuthenticateWithMagicAuthOptions,
AuthenticateWithSessionCookieFailedResponse,
AuthenticateWithSessionCookieSuccessResponse,
} from '@workos-inc/node/lib/user-management/interfaces';
import type {
AuthenticateWithOptionsBase
} from '@workos-inc/node/lib/user-management/interfaces/authenticate-with-options-base.interface';
enum WorkosExceptionReasons {
ORGANIZATION_NOT_AUTHORIZED = 'organization_not_authorized',
ORGANIZATION_SELECTION_REQUIRED = 'organization_selection_required'
}
type Organization = {
id: string;
name: string;
};
type OrganizationException = {
status: number;
rawData: {
code: WorkosExceptionReasons;
message: string;
pending_authentication_token: string;
user: UserResponse;
organizations: Organization[];
};
requestID: string;
name: string;
message: string;
};
type Secret = Pick<AuthenticateWithMagicAuthOptions, 'email' | 'code'>;
enum Roles {
ADMIN = 'admin',
MEMBER = 'member'
}
...
async #getBasicAuthOptions(): Promise<AuthenticateWithOptionsBase> {
const ipAddress = this.#findIP();
const userAgent = await this.#findUA();
return {
clientId: WORKOS_CLIENT_ID,
ipAddress,
userAgent
}
}
async authenticateWithCode(session: Secret): Promise<AuthenticationResponse> {
const options = await this.#getBasicAuthOptions();
return workos.userManagement.authenticateWithMagicAuth({
...options,
...session
});
}
async authenticateWithOrganizationSelection(
organizationId: string,
roleSlug: Roles,
session: Secret
): Promise<AuthenticationResponse> {
try {
const secret = await this.authenticateWithCode(session);
if (secret.organizationId !== organizationId) {
const userId = secret.user.id;
await workos.userManagement.createOrganizationMembership({organizationId, userId, roleSlug});
}
return secret;
}
catch (error) {
const {status, rawData} = error as OrganizationException;
const {
code,
user,
organizations = [],
pending_authentication_token
} = rawData;
const membership = organizations.some(({id}) => id === organizationId);
if (!membership) {
try {
const userId = user.id;
await workos.userManagement.createOrganizationMembership({organizationId, userId, roleSlug});
}
catch (error) {
console.error(`Couldn't create organization membership`, error);
}
}
switch (status) {
case StatusCodes.FORBIDDEN:
if (code === WorkosExceptionReasons.ORGANIZATION_SELECTION_REQUIRED) {
const options = await this.#getBasicAuthOptions();
return workos.userManagement.authenticateWithOrganizationSelection({
...options,
organizationId,
pendingAuthenticationToken: pending_authentication_token
});
}
}
throw error;
}
}
...
Yes, the feature request is to bypass letting the sign in fail and just log in to the organization directly. Because I already know the organization that the user wants to sign in to before getting the error response.