Recently, I was asked to architect and implement a multi-tenant SaaS application in NestJS with the requirement to use Auth0 for authentication.
This was my first time working with Auth0, and at first I was confused by its multi-tenant features. After some digging, I realized an important distinction: my application’s tenants were not the same as Auth0 tenants.
Auth0 tenants are intended to separate environments (like development, staging, and production), while organizations within a single Auth0 tenant are what you use to represent your app’s actual tenants.
Here’s how I integrated it step by step
1. Set Up Your NestJS App
If you haven’t already, set up a basic NestJS app. I’ll use starter code for this example.
2. Create an Auth0 Account
Log into Auth0 (or create an account). If it’s a new account, you’ll be asked if it’s for a company or personal use. In my case, I chose a personal.
3. Create an Application
Next, you’ll be prompted to create your first app, or you can navigate to Applications on the left menu and create it from there.
If you already have a frontend/client, pick it. For this example, I’ll use Regular Web Application.
4. Configure Application Settings
Navigate straight to Settings tab, note down your Domain, Client ID and Client Secret as you'll need them to authenticate your client to your backend API. Next, navigate to Login Experience tab, select Business Users, leave No prompt selected and save.
Scroll down to Application URIs and configure:
-
Application Login URI:
https://127.0.0.1:3000/login
-
Allowed Callback URLs:
https://127.0.0.1:3000/callback
-
Allowed Logout URLs:
https://127.0.0.1:3000/logout
Use https and 127.0.0.1 instead of localhost to avoid errors. And then save.
5. Create an API in Auth0
Navigate to APIs in the left menu. Click Create API, give it a name and identifier (this identifier becomes the audience parameter in authorization calls).
I called mine https://api.myapp.com
. You can name it whatever you want because it won't ever be called.
6. Create an Organization
Navigate to Organizations, then click Create Organization. Name it (I named mine org1). Next, navigate to Connections tab, enable your desired login methods (I used Username-Password-Authentication).
7. Invite Users to your organization
Navigate to Invitations tab and click Invite Members. Select our client app we created in step 3, enter email of a user you want to invite, select the connection you created in previous step, optionally you can also select the role if you've created them, I'll leave mine empty for now. Then click Send Invites.
Auth0 will send an email with an invitation link like:
https://127.0.0.1:3000/login?invitation=INVITATION_CODE&organization=org_YOUR_ORGANIZATION_ID&organization_name=YOUR_ORGANIZATION_NAME
8. Accepting the Invite
To accept the invite, grab the INVITATION_CODE from above link and navigate to:
https://YOUR_DOMAIN/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https://127.0.0.1:3000/callback&organization=org_YOUR_ORGANIZATION_ID&invitation=INVITATION_CODE
You’ll land on the Auth0 authorization page, create your password, and authorize the client app.
You’ll then be redirected to:
https://127.0.0.1:3000/callback?code=AUTHORIZATION_CODE
Exchange the Authorization Code for an Access Token
We’ll configure NestJS to accept Auth0 tokens and validate them. Our starter code has one endpoint /
that returns Hello World!
. Let’s protect it.
9. Install Required Packages
npm i passport @nestjs/passport passport-jwt jwks-rsa
npm i --save-dev @types/passport-jwt
10. Create JWT Strategy
auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'YOUR_ISSUER_URL.well-known/jwks.json',
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: 'YOUR_AUDIENCE',
issuer: 'YOUR_ISSUER_URL',
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
return payload;
}
}
YOUR_ISSUER_URL=https://YOUR_DOMAIN/
YOUR_AUDIENCE=YOUR_IDENTIFIER
11. Create Auth Module and import our JWT Strategy
auth/auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
providers: [JwtStrategy],
exports: [PassportModule],
})
export class AuthModule {}
12. Import Auth Module in app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [AuthModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
13. Protect the Endpoint in app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '@nestjs/passport';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@UseGuards(AuthGuard('jwt'))
@Get()
getHello(): string {
return this.appService.getHello();
}
}
14. Getting an Access Token
Use curl to exchange the authorization code:
curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data code=AUTHORIZATION_CODE \
--data client_id=YOUR_CLIENT_ID \
--data client_secret=YOUR_CLIENT_SECRET \
--data redirect_uri=https%3A%2F%2F127.0.0.1%3A3000%2Fcallback
You should receive a token like:
{"access_token":"eyJhb...THE_TOKEN...nA","expires_in":86400,"token_type":"Bearer"}%
15. Accessing User Info in NestJS
The user payload will be added to the ExecutionContext
, which you can access inside a controller or guard:
const request = context.switchToHttp().getRequest<Request & { user: { sub: string; org_id: string}>();
const user = request.user;
If you want more user data, append &scope=openid profile email
to your login url.
https://YOUR_DOMAIN/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https://127.0.0.1:3000/callback&organization=org_YOUR_ORGANIZATION_ID&scope=openid profile email
With these steps, you now have a working multi-tenant authentication setup in NestJS using Auth0 Organizations, complete with org_id in the token payload for tenant-aware logic.
That’s it! I hope this guide helps someone. If you have any questions, feel free to drop a comment. I’ll be happy to help.
Top comments (0)