Switching between organizations with Active Org
PropelAuth allows users to be in multiple organizations at once. Typically, however, users within your product only interact with one organization at a time.
This leads to an important question: how do you know which organization a user is currently interacting with?
In our frameworks that support Server Side Rendering, like Next.js, we have built-in support for the concept of an "Active Org". This is useful to keep the token size small. In our pure frontend frameworks, like Javascript and React, you can easily manage it yourself.
Active Org in Next.js
Setup
Out of the box, no changes are required to start using Active Org. However, we do recommend updating your route.ts
file located in app/api/auth/[slug]
.
There’s a new function getDefaultActiveOrgId
which will let you choose an activeOrgId when the user first lands in the product. Here is an example where we sort a user’s orgs alphabetically and then set the first one as active. This is a modified version of what we have in our Next.js Quickstart Guide.
import { UserFromToken } from '@propelauth/nextjs/server'
import { getRouteHandlers } from '@propelauth/nextjs/server/app-router'
import { NextRequest } from 'next/server'
// postLoginRedirectPathFn is optional, but if you want to redirect the user to a different page after login, you can do so here.
const routeHandlers = getRouteHandlers({
postLoginRedirectPathFn: (req: NextRequest) => {
return '/'
},
getDefaultActiveOrgId: (req: NextRequest, user: UserFromToken) => {
const orgs = user.getOrgs().sort((a, b) => {
return a.orgName.localeCompare(b.orgName)
})
if (orgs.length > 0) {
return orgs[0].orgId
} else {
return undefined
}
},
})
export const GET = routeHandlers.getRouteHandler
export const POST = routeHandlers.postRouteHandler
getDefaultActiveOrgId
will only be called when the user logs in, and only if you haven't already set an active organization for them previously (or if they are no longer in that organization).
The result is that everywhere you use UserFromToken
, you can now use getActiveOrg
to get this active org (more on this later).
While this is a simple example, you can customize this function to fit your needs. For example, you could:
- Pick an org based on the subdomain or domain the user is on.
- Pick an org based on a cookie you saved.
- Not use a default Active Org if you want your users to start out in all organizations. You can then manage the concept yourself outside the token.
Using an Active Org
To check which org is set to active, use the user object's getActiveOrg
or getActiveOrgId
functions. This will return:
const { user } = useUser()
if (user) {
const activeOrg = user.getActiveOrg()
// Check if the user is an admin for the active org
const isAdmin = activeOrg.isRole("Admin")
// Check if the user has a specific permission for the active org
const canApproveReports = activeOrg.hasPermission("can_approve_reports")
console.log(activeOrg)
// {
// "orgId": "1badd7f5-142a-4288-af52-5cfb9cb6c268",
// "orgName": "PropelAuth",
// "orgMetadata": {},
// "urlSafeOrgName": "propelauth",
// "userAssignedRole": "Admin",
// "userInheritedRolesPlusCurrentRole": [
// "Admin",
// "Owner",
// "Member"
// ],
// "userPermissions": [
// "propelauth::can_invite",
// "propelauth::can_change_roles",
// "propelauth::can_remove_users",
// "propelauth::can_setup_saml",
// "propelauth::can_manage_api_keys",
// ]
// }
}
Setting an Active Org
You can set an Active Org by using the setActiveOrg
function provided by useUser, which takes in an org's ID.
const { loading, user, setActiveOrg } = useUser()
// You can fetch all the orgs the user is in via
// the fetchUser API (https://docs.propelauth.com/reference/api/user#fetch-user)
// or sometimes you may want to use an env variable for isolated tenants
setActiveOrg("83e0b36a-db6d-4b9a-9bb9-0dfb75734570")
If you set an active org, there will be a small difference between user.getOrgs()
on the frontend vs the backend.
The frontend will contain all the orgs that the user is in, so you can switch between them.
The backend will only contain the active org. If you need the rest of the organizations on the backend, you'll need to fetch them.
Active Org in Javascript and React
While our Javascript and React libraries do not have a formal concept of an Active Org, you can still easily manage it yourself. For example, setting an Active Org with React or Javascript can be done in via the browser's localStorage, createContext
, or useState
.
const [activeOrg, setActiveOrg] = useState();
const {loading, userClass} = useAuthInfo();
useEffect(() => {
if (userClass && !activeOrg) {
const orgs = userClass.getOrgs()
// Or you can set the active org based on the route, a cookie, or some other logic
setActiveOrg(orgs[0])
}
}, [userClass, activeOrg]);
If you're wanting to make requests to your backend using the Active Org, you can generate an access token specific to that org using getAccessTokenForOrg
.
For this example, let's expose an endpoint to return a list of a user's resources. We only want to return the resources that are connected to that org, so we'll use the org's access token in the auth header.
async function getUserResources(accessToken, userId) {
const requestOptions = {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`
},
};
const response = await fetch(`/api/user/${userId}/resources`, requestOptions);
return await response.json();
}
const Fetcher = withRequiredAuthInfo(({tokens, user}) => {
// As an example, we'll use a custom hook to manage the active org
const [activeOrg, setActiveOrg] = useStateBackedByLocalStorage('activeOrg');
const [resources, setResources] = useState([]);
useEffect(async () => {
const accessToken = await tokens.getAccessTokenForOrg(activeOrg.orgId);
const resources = await getUserResources(accessToken, user.userId)
setResources(resources)
}, [user, activeOrg.orgId])
return <div>{JSON.stringify(resources, null, 2)}</div>
})
On our backend, we can then retrieve the active_org_id
from the User object and use it to query our database to find all the resources associated with the user_id
and active_org_id
.
@app.get("/api/user/{user_id}/resources")
async def getUser(user_id: str, user: User = Depends(require_user)):
# Get active_org_id from the org access token
active_org_id = user.get_active_org_id()
# Query db with user_id and org_id
return load_resources(user_id, active_org_id)
Questions?
Have any questions or need any help with Active Org? Don't hesitate to reach out at support@propelauth.com