DEV Community

Cover image for Integrating Auth0 Authentication with NestJS Using Organizations (Multi-Tenant Setup)
Gad Ishimwe
Gad Ishimwe

Posted on

Integrating Auth0 Authentication with NestJS Using Organizations (Multi-Tenant Setup)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • 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 {}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You should receive a token like:

{"access_token":"eyJhb...THE_TOKEN...nA","expires_in":86400,"token_type":"Bearer"}%
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)