Skip to main content
Version: Current

Auth0

Configure B2C or B2B SaaS authentication with Auth0.

Step 1: Install the package

dotnet add package PrimusSaaS.Identity.Broker

Step 2: Configure Program.cs

using PrimusSaaS.Identity.Broker;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());
builder.Services.AddControllers();

var app = builder.Build();
app.UseAuthentication(); // REQUIRED — must come before UseAuthorization
app.UseAuthorization(); // REQUIRED — without this, all endpoints are unprotected
app.UsePrimusCsrfProtection();
app.MapControllers();
app.MapPrimusAuthBroker();
app.Run();
warning

UseAuthentication() and UseAuthorization() are required in your middleware pipeline. Without them, protected endpoints such as /api/auth/me will return data to unauthenticated callers regardless of the .RequireAuthorization() annotation.

Step 3: Configure appsettings.json

{
"Auth0": {
"Domain": "your-tenant.us.auth0.com",
"ClientId": "YOUR_CLIENT_ID",
"ClientSecret": "YOUR_CLIENT_SECRET"
},
"Auth": {
"PostLoginRedirect": "/"
},
"PrimusAuth": {
"Security": {
"TokenEncryptionKey": "change-this-to-a-32-char-secret!!"
}
}
}

Configuration keys

KeyTypeRequiredExpected ValuesDescription
Auth0:DomainstringYese.g. your-tenant.us.auth0.comYour Auth0 tenant domain. Found in Auth0 Dashboard → Applications → Settings.
Auth0:ClientIdstringYesApplication Client ID GUIDThe Client ID from your Auth0 application settings.
Auth0:ClientSecretstringYesSecret value from Auth0Client secret. Use environment variables in production — never hardcode.
Auth0:OrganizationstringNoe.g. org_xxxxxxxxxxAuth0 Organization ID for B2B multi-tenancy. Omit if not using organizations. See Using Organizations below.
Auth:PostLoginRedirectstringNoAny route path, e.g. /, /dashboardWhere to redirect the user after successful login. Defaults to /.
PrimusAuth:Security:TokenEncryptionKeystringYesAny random 32+ character stringEncrypts the session cookie (AES-256). Shared across all providers. Use environment variables in production — never hardcode.
How to get these values from Auth0 Dashboard

1. Create the application

  1. Log in to the Auth0 Dashboard.
  2. Go to Applications > Applications > Create Application.
  3. Select Regular Web Application and click Create.
Application type must be Regular Web Application

The Identity Broker uses a server-side Authorization Code + PKCE flow with a client secret. Using any other type (SPA, Native, Machine to Machine) will result in a 403 Forbidden error at the /authorize endpoint.

2. Configure application URLs

In the Settings tab, set:

  • Allowed Callback URLs: http://localhost:5000/api/auth/auth0/callback
  • Allowed Logout URLs: http://localhost:5000
  • Allowed Web Origins: http://localhost:5000

Click Save Changes.

Auth0 does exact-match validation. http://localhost:5000 alone is not enough — the full /api/auth/auth0/callback path is required in Allowed Callback URLs.

3. Copy your credentials

From the Settings tab:

  • DomainAuth0:Domain
  • Client IDAuth0:ClientId
  • Client SecretAuth0:ClientSecret

Secrets via environment variables

Never put ClientSecret or TokenEncryptionKey in appsettings.json or commit them to source control. Supply them as environment variables instead. ASP.NET Core maps double-underscore (__) to the colon (:) separator:

# Linux / macOS / Docker
export Auth0__ClientSecret="your-auth0-client-secret"
export PrimusAuth__Security__TokenEncryptionKey="your-32-char-random-key"
# Windows PowerShell
$env:Auth0__ClientSecret = "your-auth0-client-secret"
$env:PrimusAuth__Security__TokenEncryptionKey = "your-32-char-random-key"
# Docker Compose / .env file (git-ignored)
Auth0__Domain=your-tenant.us.auth0.com
Auth0__ClientId=your-client-id
Auth0__ClientSecret=your-auth0-client-secret
PrimusAuth__Security__TokenEncryptionKey=your-32-char-random-key

In CI/CD (GitHub Actions, Azure DevOps), map your pipeline secrets to these same environment variable names.

Generating a strong TokenEncryptionKey
# Linux/macOS
openssl rand -base64 32

# PowerShell
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Max 256 }))

Using Organizations (optional)

The broker supports two org modes:

Static (one org per deployment) — set Auth0:Organization in config. Every login is scoped to that org.

Dynamic (multiple orgs, one deployment) — not yet supported in the current release. The broker reads only the static Auth0:Organization config value. For multi-org routing today, deploy one broker instance per organization, each with its own Auth0:Organization config. Dynamic org selection via a query parameter is on the roadmap.


For the static approach, add Organization to your config:

"Auth0": {
"Domain": "your-tenant.us.auth0.com",
"ClientId": "YOUR_CLIENT_ID",
"ClientSecret": "YOUR_CLIENT_SECRET",
"Organization": "org_xxxxxxxxxx"
}
Organization requires additional setup in Auth0 Dashboard

When Organization is set, Auth0 returns an error unless all four of these are configured:

  1. Organizations enabled on the app — Applications → your app → Login Experience tab → under "Types of Users" select "Business Users" or "Both"Save Changes. Without this, Auth0 rejects the organization parameter with parameter organization is not allowed for this client.
  2. Connections enabled — Organizations → your org → Connections tab → Enable Connections → enable at least Username-Password-Authentication.
  3. Application added to org — Organizations → your org → Overview tab → scroll to ApplicationsAdd Applications → select your app.
  4. User is a member — Organizations → your org → Members tab → add the user account you are logging in with.

If you are just testing without org enforcement, omit the Organization field entirely.

Step 4: Available endpoints

Endpoints are registered automatically by app.MapPrimusAuthBroker().

EndpointMethodDescription
GET /api/auth/providersGETReturns the list of configured providers. Also seeds the XSRF-TOKEN cookie.
GET /api/auth/auth0GETInitiates the Auth0 login redirect
GET /api/auth/meGETReturns the current authenticated user (401 if not logged in)
POST /api/auth/logoutPOSTClears the session cookie

Step 5: Test the flow

Run the app:

dotnet run --urls "http://localhost:5000"

1. Check available providers

# Linux/macOS
curl http://localhost:5000/api/auth/providers

# Windows PowerShell
Invoke-WebRequest http://localhost:5000/api/auth/providers | Select-Object -ExpandProperty Content

Expected response:

{"providers":["auth0"]}

2. Sign in via browser

Open this URL in your browser — it will redirect to the Auth0 login page:

http://localhost:5000/api/auth/auth0

Sign in with your Auth0 user credentials. After login, Auth0 redirects back to your app and the Broker sets a session cookie automatically.

3. Confirm the session

In the same browser, open:

http://localhost:5000/api/auth/me

Expected response:

{
"email": "you@example.com",
"role": "User",
"provider": "auth0"
}

4. Test a protected endpoint

Add [Authorize] to any controller action to restrict it to authenticated users only:

[HttpGet("profile")]
[Authorize]
public IActionResult Profile()
{
return Ok(new { email = User.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value });
}

With a valid session cookie → 200 OK with user data.
Without a session cookie → 401 Unauthorized.

5. Sign out

Run this in the browser DevTools console (F12) while on the app page:

const csrf = document.cookie.split('; ').find(c => c.startsWith('XSRF-TOKEN=')).split('=')[1];
fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include',
headers: { 'X-Primus-CSRF': csrf }
}).then(r => console.log('Logout:', r.status)); // Expected: 200

After logout, /api/auth/me returns 401 Unauthorized.

Why can't I just navigate to /api/auth/logout in the browser?

The logout endpoint is POST-only. Navigating to a URL in the browser sends a GET, which returns 405 Method Not Allowed and does not clear the session. The CSRF header requirement also prevents third-party pages from logging users out silently.

Notes

On Auth0 development tenants, users see a consent screen asking to approve openid profile email scopes on their first login after each logout. This is normal behaviour. To suppress it, go to Auth0 Dashboard → APIs → your API → enable "Allow Skipping User Consent".

Supported application types

Only Regular Web Application is supported. The broker is a server-side session broker — it holds session cookies on the backend and exchanges an authorization code for tokens server-side. SPA and Native flows handle tokens client-side, which is outside the broker's scope.

TokenEncryptionKey is shared across providers

PrimusAuth:Security:TokenEncryptionKey encrypts session tokens for all configured providers (Auth0, Azure AD, Google, Okta). Define it once and all providers use it.

Troubleshooting

ErrorCauseFix
403 Forbidden at /authorizeWrong application typeChange app type to Regular Web Application in Auth0 Dashboard → Applications → Settings
parameter organization is not allowed for this clientTypes of Users not set to include organizationsApplications → your app → Login Experience tab → Types of Users → select "Business Users" or "Both" → Save Changes
403 Forbidden with Organization setApp not enabled for the orgDashboard → Organizations → your org → Overview → Applications → Add your app
403 Forbidden with Organization setNo connections enabled on orgDashboard → Organizations → your org → Connections → Enable Connections
403 Forbidden with Organization setUser not a member of the orgDashboard → Organizations → your org → Members → Add the user
400 Invalid CSRF token on logoutSending a hardcoded value like '1'Read the XSRF-TOKEN cookie value and send it in the X-Primus-CSRF header
405 Method Not Allowed on logoutNavigating to logout URL in browser (GET)Call logout as a POST from JavaScript — see Step 5 above
Consent screen appears after every logoutAuth0 development tenant behaviourEnable "Allow Skipping User Consent" on the API in Auth0 Dashboard

SAML 2.0 (Enterprise SSO)

Auth0 can act as a SAML 2.0 Identity Provider, which lets you use the Primus SAML integration while Auth0 handles the upstream user directory. This is common when your enterprise customers authenticate against Auth0 organizations and you want a standards-based SAML assertion flowing into your app.

Use IdpType: Custom and point MetadataUrl at your Auth0 tenant's SAML metadata endpoint:

appsettings.json
{
"PrimusAuth": {
"Saml": {
"EntityId": "https://your-app.example.com",
"IdentityProviders": [
{
"Scheme": "auth0-saml",
"DisplayName": "Auth0 (SAML)",
"IdpType": "Custom",
"MetadataUrl": "https://YOUR_DOMAIN.auth0.com/samlp/metadata/YOUR_CLIENT_ID"
}
]
}
}
}

Replace YOUR_DOMAIN with your Auth0 tenant domain (e.g. my-tenant.us.auth0.com) and YOUR_CLIENT_ID with the Client ID of the Auth0 application you have configured as a SAML IdP.

Auth0 Dashboard setup required

To use Auth0 as a SAML IdP, you must enable the SAML2 Web App addon on the application in Auth0 Dashboard → Applications → your app → Addons tab. Set the Application Callback URL to https://your-app.example.com/api/auth/auth0-saml/callback.

For full SAML configuration options see the SAML 2.0 page.


Advanced features

The following features work with Auth0 exactly the same way as any other provider:


Next Steps