Open-source SSO, OAuth 2.0, and OIDC server designed for modern security and seamless multi-tenant integration.
Understanding the authentication flow and architecture
Common authentication challenges, solved out of the box
Challenge: Managing multiple tenants with isolated data
Solution: Built-in multi-tenancy with complete data isolation, tenant-specific roles, and custom branding per tenant.
Challenge: Users need to login to multiple internal apps
Solution: Single Sign-On across all applications. Login once, access everything. Single logout clears all sessions.
Challenge: Different users need different access levels
Solution: Granular RBAC system with role hierarchies, permission-based menu filtering, and API-level authorization.
Challenge: Building auth from scratch takes weeks
Solution: Deploy DoorAuth in minutes. Integrate with your app in under an hour. Focus on your business logic, not auth.
Unique capabilities that set DoorAuth apart
Dynamic, permission-based navigation
DoorAuth automatically generates navigation menus based on user permissions. Each user sees only the menu items they're authorized to access.
GET /api/menus/smart
{
"menus": [
{
"id": 1,
"label": "Dashboard",
"icon": "đ",
"route": "/dashboard",
"order": 1
},
{
"id": 2,
"label": "Users",
"icon": "đĨ",
"route": "/users",
"order": 2,
"children": [
{ "label": "View Users", "route": "/users/list" },
{ "label": "Add User", "route": "/users/add" }
]
}
]
}
Enterprise-grade multi-tenancy
Complete tenant isolation system that allows you to serve multiple customers (tenants) from a single DoorAuth instance with guaranteed data separation.
Each tenant's data is completely separated. Users can only access their tenant's data.
Define different roles and permissions for each tenant independently.
Each tenant can have custom branding, logos, and themes.
Track usage, users, and activity per tenant.
Automatic token refresh with rotation for enhanced security. Refresh tokens expire after 7 days and rotate on each use.
Built-in email verification system with customizable templates. Secure token-based verification with expiration.
Automatic account locking after failed login attempts. Configurable thresholds and lockout duration.
Time-based One-Time Password authentication. Compatible with Google Authenticator, Authy, and other TOTP apps.
Enterprise-grade security features out of the box
Full OpenID Connect provider with PKCE security, authorization code flow, and refresh tokens.
Complete tenant isolation with dedicated data spaces. Perfect for SaaS applications.
Role-based access control with granular permissions and dynamic menu generation.
Login once, access all applications. Centralized authentication with SSO logout.
Enterprise-grade security with 2FA, brute force protection, and account locking.
Beautiful React-based admin panel for managing users, tenants, apps, and roles.
Get your authentication server running in less than 5 minutes
Get the source code from GitHub
git clone https://github.com/alaminmain/DoorAuthServer.git
cd DoorAuthServer
Install server and client packages
# Install server dependencies
cd server
npm install
# Install client dependencies
cd ../client
npm install
Initialize PostgreSQL database with Prisma
cd server
npx prisma migrate dev
npx prisma db seed
Run the development server
# Start backend
npm run dev
# In another terminal, start frontend
cd ../client
npm run dev
https://localhost:3000
https://localhost:3000/api-docs
https://localhost:3000
npx prisma studio
bd@gmail.com
1q2w3E*
Default (ID: 1)
Step-by-step integration for your application framework
Add OpenID Connect authentication packages to your Blazor project
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect --version 9.0.0
dotnet add package Microsoft.AspNetCore.Authentication.Cookies --version 2.2.0
Add your application to the DoorAuth database
// In server/prisma/seed.ts, add:
{
clientId: 'your-app-client-id',
clientSecret: 'your-app-secret-key',
name: 'Your Application Name',
appUrl: 'https://localhost:7231',
redirectUris: 'https://localhost:7231/signin-oidc',
grantTypes: 'authorization_code,refresh_token',
scopes: 'openid,profile,email',
tenantId: 1,
isActive: true
}
Add OIDC authentication to your application
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://localhost:3000";
options.ClientId = "your-app-client-id";
options.ClientSecret = "your-app-secret-key";
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.UsePkce = true;
});
Enable authentication in your application pipeline
app.UseAuthentication();
app.UseAuthorization();
// Login endpoint
app.MapGet("/login", () => Results.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
new[] { OpenIdConnectDefaults.AuthenticationScheme }
));
// Logout endpoint
app.MapGet("/logout", async (HttpContext context) =>
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
});
Add authorization to your Razor components
@page "/dashboard"
@attribute [Authorize]
<h1>Welcome, @context.User.Identity?.Name</h1>
<AuthorizeView>
<Authorized>
<p>You are logged in!</p>
</Authorized>
<NotAuthorized>
<a href="/login">Please login</a>
</NotAuthorized>
</AuthorizeView>
Create a new ASP.NET Core Razor Pages or MVC application
# Create new Razor Pages project
dotnet new webapp -n MyApp
cd MyApp
# Add OIDC authentication package
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
Add your application to DoorAuth via Admin Dashboard or database seed
// In server/prisma/seed.ts, add:
{
clientId: 'door-auth-sample',
clientSecret: 'sample-secret-key',
name: 'DoorAuth Portal',
appUrl: 'https://localhost:7140',
redirectUris: 'https://localhost:7140/signin-oidc',
grantTypes: 'authorization_code,refresh_token',
scopes: 'openid,profile,email',
tenantId: 1,
isActive: true
}
Set up OpenID Connect authentication with DoorAuth endpoints
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
var builder = WebApplication.CreateBuilder(args);
// Configure Cookie settings for cross-site SSO
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.Always;
});
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
})
.AddOpenIdConnect(options =>
{
options.ClientId = "door-auth-sample";
options.ClientSecret = "sample-secret-key";
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.UsePkce = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
// Configure DoorAuth OIDC endpoints
options.Configuration = new OpenIdConnectConfiguration
{
Issuer = "https://localhost:3000",
AuthorizationEndpoint = "https://localhost:3000/api/oauth/authorize",
TokenEndpoint = "https://localhost:3000/api/oauth/token",
UserInfoEndpoint = "https://localhost:3000/api/oauth/userinfo",
EndSessionEndpoint = "https://localhost:3000/api/oauth/end_session",
JwksUri = "https://localhost:3000/.well-known/jwks.json"
};
});
Enable authentication and authorization in the request pipeline
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Add endpoints for triggering authentication challenges and sign-out
// Login endpoint - triggers OIDC flow
app.MapGet("/login", () => Results.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
new[] { OpenIdConnectDefaults.AuthenticationScheme }
));
// Logout endpoint - signs out from both app and DoorAuth
app.MapGet("/logout", async (HttpContext context) =>
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties { RedirectUri = "/" });
});
Retrieve authenticated user claims and access token for API calls
// In a Razor Page or Controller
public class IndexModel : PageModel
{
public string UserName { get; set; }
public string AccessToken { get; set; }
public async Task OnGetAsync()
{
if (User.Identity?.IsAuthenticated == true)
{
// Get user claims
UserName = User.Identity.Name;
var email = User.FindFirst("email")?.Value;
var tenantId = User.FindFirst("tenantId")?.Value;
// Get access token for API calls
AccessToken = await HttpContext.GetTokenAsync("access_token");
// Call DoorAuth API with token
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", AccessToken);
var response = await client.GetAsync(
"https://localhost:3000/api/users/me/applications");
}
}
}
Add authorization to your Razor pages
// Pages/Dashboard.cshtml.cs
[Authorize]
public class DashboardModel : PageModel
{
public void OnGet()
{
// Only authenticated users can access this page
}
}
// Pages/Dashboard.cshtml
@page
@model DashboardModel
@{
ViewData["Title"] = "Dashboard";
}
<h1>Welcome, @User.Identity?.Name!</h1>
@if (User.Identity?.IsAuthenticated == true)
{
<p>Email: @User.FindFirst("email")?.Value</p>
<a href="/logout">Logout</a>
}
else
{
<a href="/login">Login with DoorAuth</a>
}
See the complete SSO Portal implementation in the DoorAuthSample project. It demonstrates fetching user applications and launching them with SSO tokens.
Set up a new React project with Vite and install required packages
# Create new React + TypeScript project
npm create vite@latest my-app -- --template react-ts
cd my-app
# Install required dependencies
npm install axios react-router-dom jwt-decode
Add your React SPA to DoorAuth via Admin Dashboard or database
// In server/prisma/seed.ts, add:
{
clientId: 'todo-app-client',
clientSecret: 'todo-secret-key',
name: 'Todo App',
appUrl: 'http://localhost:5175',
redirectUris: 'http://localhost:5175/callback',
grantTypes: 'authorization_code,refresh_token',
scopes: 'openid,profile,email',
tenantId: 1,
isActive: true
}
Define OAuth configuration settings for DoorAuth
// src/auth/authConfig.ts
export const authConfig = {
authority: 'https://localhost:3000', // DoorAuth server
clientId: 'todo-app-client', // Your client ID
redirectUri: 'http://localhost:5175/callback',
responseType: 'code', // Authorization code flow
scope: 'openid profile email', // Requested scopes
// OAuth endpoints
authorizationEndpoint: '/api/oauth/authorize',
tokenEndpoint: '/api/oauth/token',
userInfoEndpoint: '/api/oauth/userinfo',
endSessionEndpoint: '/api/oauth/end_session'
};
Create the authentication service with PKCE security (prevents code interception attacks)
// src/services/AuthService.ts
import { authConfig } from '../auth/authConfig';
class AuthService {
// Generate cryptographically secure PKCE verifier
private generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte =>
byte.toString(16).padStart(2, '0')).join('');
}
// Create SHA-256 challenge from verifier
private async generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// Initiate OAuth login with PKCE
async login(): Promise<void> {
const verifier = this.generateCodeVerifier();
const challenge = await this.generateCodeChallenge(verifier);
// Store verifier for token exchange
localStorage.setItem('pkce_verifier', verifier);
// Build authorization URL
const params = new URLSearchParams({
client_id: authConfig.clientId,
redirect_uri: authConfig.redirectUri,
response_type: authConfig.responseType,
scope: authConfig.scope,
code_challenge: challenge,
code_challenge_method: 'S256'
});
// Redirect to DoorAuth
window.location.href =
`${authConfig.authority}${authConfig.authorizationEndpoint}?${params}`;
}
// Exchange authorization code for tokens
async handleCallback(code: string): Promise<string> {
const verifier = localStorage.getItem('pkce_verifier');
if (!verifier) throw new Error('No PKCE verifier found');
const response = await fetch(
`${authConfig.authority}${authConfig.tokenEndpoint}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: authConfig.redirectUri,
client_id: authConfig.clientId,
code_verifier: verifier
})
}
);
const data = await response.json();
localStorage.removeItem('pkce_verifier');
localStorage.setItem('access_token', data.access_token);
return data.access_token;
}
// Get stored token
getToken(): string | null {
return localStorage.getItem('access_token');
}
// Logout and redirect
logout(): void {
localStorage.removeItem('access_token');
window.location.href =
`${authConfig.authority}${authConfig.endSessionEndpoint}`;
}
}
export const authService = new AuthService();
Set up React Context for global authentication state management
// src/auth/AuthProvider.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { jwtDecode } from 'jwt-decode';
import { authService } from '../services/AuthService';
interface User {
userId: string;
email: string;
name: string;
tenantId: number;
roles: string[];
permissions: string[];
}
interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
login: () => void;
logout: () => void;
handleCallback: (code: string) => Promise<void>;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Check for existing token on mount
const token = authService.getToken();
if (token) {
try {
const decoded = jwtDecode<User & { exp: number }>(token);
if (decoded.exp * 1000 > Date.now()) {
setUser(decoded);
} else {
authService.logout();
}
} catch {
authService.logout();
}
}
setIsLoading(false);
}, []);
const handleCallback = async (code: string) => {
const token = await authService.handleCallback(code);
const decoded = jwtDecode<User>(token);
setUser(decoded);
};
return (
<AuthContext.Provider value={{
user,
isAuthenticated: !!user,
isLoading,
login: () => authService.login(),
logout: () => authService.logout(),
handleCallback
}}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
};
Handle OAuth callback and protect routes requiring authentication
// src/pages/Callback.tsx - Handle OAuth redirect
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useAuth } from '../auth/AuthProvider';
export function Callback() {
const [searchParams] = useSearchParams();
const { handleCallback } = useAuth();
const navigate = useNavigate();
useEffect(() => {
const code = searchParams.get('code');
if (code) {
handleCallback(code)
.then(() => navigate('/'))
.catch(() => navigate('/login'));
}
}, [searchParams, handleCallback, navigate]);
return <div>Authenticating...</div>;
}
// src/components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '../auth/AuthProvider';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) return <Navigate to="/login" replace />;
return <>{children}</>;
}
Configure React Router with authentication routes
// src/App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './auth/AuthProvider';
import { ProtectedRoute } from './components/ProtectedRoute';
import { Login } from './pages/Login';
import { Callback } from './pages/Callback';
import { Dashboard } from './pages/Dashboard';
function App() {
return (
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/callback" element={<Callback />} />
<Route path="/" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
</Routes>
</AuthProvider>
</BrowserRouter>
);
}
export default App;
Build the UI components for login and protected dashboard
// src/pages/Login.tsx
import { useAuth } from '../auth/AuthProvider';
export function Login() {
const { login } = useAuth();
return (
<div className="login-container">
<h1>Welcome to My App</h1>
<p>Please login to continue</p>
<button onClick={login}>
Login with DoorAuth
</button>
</div>
);
}
// src/pages/Dashboard.tsx
import { useAuth } from '../auth/AuthProvider';
export function Dashboard() {
const { user, logout } = useAuth();
return (
<div className="dashboard">
<h1>Welcome, {user?.name}!</h1>
<div className="user-info">
<p><strong>Email:</strong> {user?.email}</p>
<p><strong>Tenant ID:</strong> {user?.tenantId}</p>
<p><strong>Roles:</strong> {user?.roles?.join(', ')}</p>
</div>
<button onClick={logout}>Logout</button>
</div>
);
}
See the complete React SPA implementation with SSO Portal integration in the client_todo project. It includes PKCE flow, SSO token handling, and centralized logout.
Add Passport.js with OpenID Connect strategy
npm install express passport passport-openidconnect express-session
Set up OpenID Connect strategy with DoorAuth endpoints
const passport = require('passport');
const OpenIDConnectStrategy = require('passport-openidconnect');
passport.use('oidc', new OpenIDConnectStrategy({
issuer: 'https://localhost:3000',
authorizationURL: 'https://localhost:3000/api/oauth/authorize',
tokenURL: 'https://localhost:3000/api/oauth/token',
userInfoURL: 'https://localhost:3000/api/oauth/userinfo',
clientID: 'your-client-id',
clientSecret: 'your-client-secret',
callbackURL: 'http://localhost:4000/callback',
scope: ['openid', 'profile', 'email']
}, (issuer, profile, done) => {
return done(null, profile);
}));
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
Configure Express with session and Passport middleware
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'your-session-secret',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
// Login route
app.get('/login', passport.authenticate('oidc'));
// Callback route
app.get('/callback',
passport.authenticate('oidc', { failureRedirect: '/login' }),
(req, res) => res.redirect('/')
);
// Logout route
app.get('/logout', (req, res) => {
req.logout(() => {
res.redirect('https://localhost:3000/api/oauth/end_session');
});
});
// Protected route
app.get('/', ensureAuthenticated, (req, res) => {
res.json({ user: req.user });
});
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect('/login');
}
app.listen(4000, () => console.log('App running on http://localhost:4000'));
For direct API integration without Passport.js, visit the Swagger documentation when the server is running.
Build an SSO portal that launches applications with seamless authentication
The DoorAuthSample demonstrates a powerful SSO Portal pattern where users login once and can launch multiple applications without re-authenticating.
// In DoorAuthSample portal - launch app with SSO token
function launchApp(appUrl, appName) {
// Get access token from OIDC session
const token = document.getElementById('accessToken').value;
// Construct SSO URL with token
const cleanAppUrl = appUrl.endsWith('/') ? appUrl.slice(0, -1) : appUrl;
const ssoUrl = `${cleanAppUrl}/sso?token=${encodeURIComponent(token)}`;
// Launch the application
window.location.href = ssoUrl;
}
// src/pages/SSOHandler.tsx - Handle token from portal
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useAuth } from '../auth/AuthProvider';
export function SSOHandler() {
const [searchParams] = useSearchParams();
const { handleSSOToken } = useAuth();
const navigate = useNavigate();
useEffect(() => {
const token = searchParams.get('token');
if (token) {
// Validate and store the token
handleSSOToken(token)
.then(() => navigate('/'))
.catch(() => navigate('/login'));
} else {
navigate('/login');
}
}, [searchParams, handleSSOToken, navigate]);
return <div>Processing SSO login...</div>;
}
// Add route: <Route path="/sso" element={<SSOHandler />} />
// React App - Logout redirects to portal
const logout = () => {
localStorage.removeItem('access_token');
// Redirect to portal logout (which logs out from DoorAuth too)
window.location.href = 'https://localhost:7140/logout';
};
// Portal (ASP.NET) - Cascading logout
app.MapGet("/logout", async (HttpContext context) => {
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
// This triggers DoorAuth end_session, logging out everywhere
});
Users authenticate once and access all applications
No login prompts when launching apps from portal
Single logout clears sessions across all applications
Portal shows only apps user has access to
40+ RESTful endpoints with full Swagger documentation
/api/auth/register
/api/auth/login
/api/auth/logout
/api/oauth/authorize
/api/oauth/token
/api/oauth/userinfo
/api/oauth/revoke
/api/oauth/end_session
/api/tenants
/api/tenants/:id
/api/tenants
/api/tenants/:id
/api/tenants/:id
/api/applications
/api/applications
/api/applications/:id
/api/applications/:id
/api/applications/:id/regenerate-secret
/api/roles
/api/roles
/api/roles/:id
/api/roles/:id
/api/roles/:id/permissions
/api/2fa/generate
/api/2fa/verify
/api/password/forgot-password
/api/password/reset-password
Everything you need to integrate and deploy DoorAuth
Get up and running in minutes with step-by-step instructions
Read Guide âComplete guide for integrating DoorAuth into your applications
Read Guide âOpenID Connect integration for .NET and JavaScript applications
Read Guide âTwo-Factor Authentication implementation and testing guide
Read Guide âUnit, integration, and E2E testing documentation
Read Guide âCommon issues and solutions for DoorAuth integration
Read Guide â