مستند OIDC/SSO: مکمل انٹیگریشن گائیڈ
Authentik ایک اوپن سورس شناخت فراہم کنندہ ہے جو آپ کو Okta یا Auth0 کی پیچیدگی (یا قیمت) کے بغیر انٹرپرائز گریڈ SSO, OIDC, SAML اور OAuth2 فراہم کرتا ہے۔ خود میزبان ایپلی کیشنز کے لیے، یہ Authentik کے OIDC اینڈ پوائنٹس، صارف کا انتظام، گروپ پر مبنی رسائی کنٹرول، اور انٹرپرائز پالیسیاں فراہم کرتا ہے — یہ سب آپ کے اپنے بنیادی ڈھانچے کے تحت ہیں۔
اس گائیڈ میں Next.js 16 اور NestJS 11 کے ساتھ پروڈکشن Authentik انضمام کا احاطہ کیا گیا ہے: OAuth2 فراہم کنندہ کنفیگریشن، محفوظ اجازت کوڈ کا بہاؤ، ایک بار کے تبادلے کے کوڈ کا پیٹرن، JWT کی توثیق، اور ایسے لطیف مسائل جو تصدیق کے نفاذ کو بناتے یا توڑتے ہیں۔
اہم ٹیک ویز
- توثیق ٹوکنز کو HttpOnly کوکیز میں رہنا چاہیے — کبھی بھی لوکل اسٹوریج یا استفسار کے پیرامیٹرز میں نہیں
- کال بیک سے فرنٹ اینڈ تک ٹوکن منتقل کرنے کے لیے ایک وقتی تبادلے کا کوڈ (60-سیکنڈ TTL) استعمال کریں
- سرور سے سرور ٹوکن ایکسچینج کے لیے ہمیشہ
AUTHENTIK_INTERNAL_URLاستعمال کریں (نیٹ ورک ہاپس سے بچیں)- کیشے سے حذف کرنے سے پہلے ایکسچینج کوڈ کی میعاد ختم ہونے کی جانچ ہونی چاہیے (نسل کی حالت کی روک تھام)
- API کے ذریعے دوبارہ تخلیق کیے گئے مستند کلائنٹ کے رازوں میں ہیشنگ کی مماثلت ہو سکتی ہے — Django ORM کو براہ راست استعمال کریں
- JWT دعووں میں
organizationIdشامل ہونا چاہیے — اسے Authentik پراپرٹی میپنگ میں انکوڈ کریں- کوکیز سے JWT نکالنے سے پہلے NestJS میں
cookie-parserمڈل ویئر کی ضرورت ہے۔- OIDC دریافت اختتامی نقطہ تمام ٹوکن/یوزر انفو یو آر ایل فراہم کرتا ہے — انہیں ہارڈ کوڈ نہ کریں۔
مستند کنفیگریشن
OAuth2 فراہم کنندہ بنانا
Authentik ایڈمن پینل میں (/if/admin/):
- درخواستیں > فراہم کنندگان > تخلیق پر جائیں
- OAuth2/OpenID فراہم کنندہ کو منتخب کریں
- ترتیب دیں:
- نام:
ECOSIRE Web OAuth2 - اجازت کا بہاؤ:
default-provider-authorization-implicit-consent - کلائنٹ کی قسم:
Confidential - کلائنٹ ID:
ecosire-web(آپ اسے منتخب کرتے ہیں) - ری ڈائریکٹ URIs:
https://api.ecosire.com/api/auth/callback https://ecosire.com/auth/callback - سائننگ کی: اپنا ڈیفالٹ سرٹیفکیٹ منتخب کریں۔
- اسکوپس:
openid,email,profile
- ایک ایپلیکیشن بنائیں جو اس فراہم کنندہ کو استعمال کرتی ہو۔
اپنی مرضی کے مطابق پراپرٹی میپنگ
JWT میں organizationId کو شامل کرنے کے لیے، پراپرٹی میپنگ بنائیں:
مستند ایڈمن> حسب ضرورت> پراپرٹی میپنگ> بنائیں> اسکوپ میپنگ میں:
# Name: Organization ID Scope
# Scope name: organization
return {
"organizationId": request.user.attributes.get("organizationId", str(request.user.pk)),
"name": request.user.name,
}
اس میپنگ کو اپنے OAuth2 فراہم کنندہ کی "Scopes" کی فہرست میں شامل کریں اور organization کو اپنی درخواست کے ذریعہ درخواست کردہ اسکوپس میں شامل کریں۔
پس منظر: NestJS Auth ماڈیول
JWT حکمت عملی
// auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } 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({
// Support both Cookie and Bearer token
jwtFromRequest: ExtractJwt.fromExtractors([
// Cookie first (web app)
(request) => {
return request?.cookies?.ecosire_auth ?? null;
},
// Bearer fallback (API clients, mobile)
ExtractJwt.fromAuthHeaderAsBearerToken(),
]),
ignoreExpiration: false,
// Validate against Authentik's public JWKS endpoint
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${process.env.AUTHENTIK_INTERNAL_URL}/application/o/ecosire-web/jwks/`,
}),
audience: 'ecosire-web',
issuer: `${process.env.AUTHENTIK_URL}/application/o/ecosire-web/`,
});
}
async validate(payload: {
sub: string;
email: string;
name: string;
organizationId?: string;
groups?: string[];
}) {
if (!payload.sub) {
throw new UnauthorizedException('Invalid token');
}
// Map Authentik claims to your internal user type
return {
sub: payload.sub,
email: payload.email,
name: payload.name,
organizationId: payload.organizationId ?? payload.sub,
role: this.mapGroupsToRole(payload.groups ?? []),
};
}
private mapGroupsToRole(groups: string[]): 'admin' | 'support' | 'user' {
if (groups.includes('ecosire-admins')) return 'admin';
if (groups.includes('ecosire-support')) return 'support';
return 'user';
}
}
کوکی کنفیگریشن
// main.ts
import cookieParser from 'cookie-parser';
const app = await NestFactory.create(AppModule);
// cookie-parser MUST be registered before JWT strategy runs
app.use(cookieParser(process.env.COOKIE_SECRET));
اوتھ کنٹرولر
// auth/auth.controller.ts
import {
Controller,
Get,
Post,
Query,
Res,
Req,
Body,
UnauthorizedException,
} from '@nestjs/common';
import { Response, Request } from 'express';
import { Public } from './decorators/public.decorator';
import { AuthService } from './auth.service';
const COOKIE_OPTS = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax' as const,
path: '/',
maxAge: 60 * 60 * 24 * 7 * 1000, // 7 days
};
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Get('login')
@Public()
login(@Res() res: Response) {
const params = new URLSearchParams({
client_id: process.env.AUTHENTIK_CLIENT_ID!,
redirect_uri: `${process.env.API_URL}/auth/callback`,
response_type: 'code',
scope: 'openid email profile organization',
state: this.authService.generateState(),
});
const authUrl = `${process.env.AUTHENTIK_URL}/application/o/authorize/?${params}`;
return res.redirect(authUrl);
}
@Get('callback')
@Public()
async callback(
@Query('code') code: string,
@Query('state') state: string,
@Res() res: Response
) {
// Exchange authorization code for tokens
const { accessToken, refreshToken } = await this.authService.exchangeCode(code, state);
// Create a one-time exchange code for the frontend
// This avoids putting the token in the redirect URL
const exchangeCode = await this.authService.createExchangeCode(accessToken, refreshToken);
// Redirect to frontend with the one-time code (not the token itself)
return res.redirect(
`${process.env.FRONTEND_URL}/auth/callback?exchange=${exchangeCode}`
);
}
@Post('exchange')
@Public()
async exchangeTokens(
@Body() body: { code: string },
@Res() res: Response
) {
// Frontend exchanges the one-time code for HttpOnly cookies
const tokens = await this.authService.redeemExchangeCode(body.code);
if (!tokens) {
throw new UnauthorizedException('Invalid or expired exchange code');
}
// Set HttpOnly cookies — tokens never in response body
res.cookie('ecosire_auth', tokens.accessToken, COOKIE_OPTS);
res.cookie('ecosire_refresh', tokens.refreshToken, {
...COOKIE_OPTS,
path: '/auth/refresh', // Refresh token only sent to refresh endpoint
});
return res.json({ success: true });
}
@Get('session')
async getSession(@Req() req: Request) {
// JWT guard already validated the cookie, user attached to req
return req.user; // sub, email, name, role, organizationId
}
@Post('logout')
async logout(@Res() res: Response) {
// Clear cookies with the SAME options used to set them
res.clearCookie('ecosire_auth', COOKIE_OPTS);
res.clearCookie('ecosire_refresh', {
...COOKIE_OPTS,
path: '/auth/refresh',
});
// Optionally: End Authentik session
const logoutUrl = `${process.env.AUTHENTIK_URL}/application/o/ecosire-web/end-session/`;
return res.json({ logoutUrl });
}
}
توثیق سروس: ایکسچینج کوڈ پیٹرن
ون ٹائم ایکسچینج کوڈ ٹوکن ان یو آر ایل سے بچنے کی کلید ہے (جو براؤزر کی سرگزشت، سرور لاگز، اور ریفرر ہیڈر میں ظاہر ہوتا ہے):
// auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Redis } from 'ioredis';
import { nanoid } from 'nanoid';
import axios from 'axios';
@Injectable()
export class AuthService {
constructor(private redis: Redis) {}
async exchangeCode(code: string, state: string) {
// Verify state matches (CSRF protection)
const storedState = await this.redis.get(`auth:state:${state}`);
if (!storedState) {
throw new UnauthorizedException('Invalid state parameter');
}
await this.redis.del(`auth:state:${state}`);
// Exchange authorization code for tokens using internal URL
// AUTHENTIK_INTERNAL_URL avoids going through Nginx/Cloudflare for
// server-to-server calls inside the same network
const response = await axios.post(
`${process.env.AUTHENTIK_INTERNAL_URL}/application/o/token/`,
new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: `${process.env.API_URL}/auth/callback`,
client_id: process.env.AUTHENTIK_CLIENT_ID!,
client_secret: process.env.AUTHENTIK_CLIENT_SECRET!,
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
};
}
async createExchangeCode(
accessToken: string,
refreshToken: string
): Promise<string> {
const code = nanoid(32);
const payload = JSON.stringify({ accessToken, refreshToken });
// 60-second TTL — enough for the redirect to complete
await this.redis.setex(`auth:exchange:${code}`, 60, payload);
return code;
}
async redeemExchangeCode(code: string) {
const key = `auth:exchange:${code}`;
// Check expiry BEFORE deleting (prevents race condition)
const ttl = await this.redis.ttl(key);
if (ttl <= 0) {
return null; // Already expired or doesn't exist
}
const data = await this.redis.getdel(key); // Atomic get-and-delete
if (!data) return null;
return JSON.parse(data);
}
generateState(): string {
const state = nanoid(32);
// Store state with 10-minute TTL
this.redis.setex(`auth:state:${state}`, 600, '1');
return state;
}
async upsertUser(payload: {
sub: string;
email: string;
name: string;
organizationId: string;
}) {
const [user] = await db
.insert(users)
.values({
id: payload.sub,
email: payload.email,
name: payload.name,
organizationId: payload.organizationId,
})
.onConflictDoUpdate({
target: users.id,
set: {
email: payload.email,
name: payload.name,
lastLoginAt: new Date(),
},
})
.returning();
return user;
}
}
فرنٹ اینڈ: کال بیک ہینڈلر
// app/auth/callback/page.tsx — Next.js page
'use client';
import { useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
export default function AuthCallbackPage() {
const router = useRouter();
const params = useSearchParams();
useEffect(() => {
const exchangeCode = params.get('exchange');
const redirectTo = params.get('redirect') ?? '/dashboard';
if (!exchangeCode) {
router.push('/auth/login?error=no_code');
return;
}
// Prevent open redirect
const safeRedirect = redirectTo.startsWith('/') && !redirectTo.startsWith('//')
? redirectTo
: '/dashboard';
// Exchange one-time code for HttpOnly cookies
fetch('/api/auth/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: exchangeCode }),
credentials: 'include', // Required for cookie setting
})
.then((res) => {
if (res.ok) {
router.push(safeRedirect);
} else {
router.push('/auth/login?error=exchange_failed');
}
})
.catch(() => router.push('/auth/login?error=network'));
}, []);
return (
<div className="flex items-center justify-center min-h-screen">
<p>Signing you in...</p>
</div>
);
}
کلائنٹ کے خفیہ ہیشنگ کے مسئلے کو ٹھیک کرنا
ایک عام مستند مسئلہ: REST API کے ذریعے سیٹ کیے گئے کلائنٹ کے رازوں میں بعض اوقات ہیشنگ مماثلت ہوتی ہے اور OIDC ٹوکن ایکسچینج ناکام ہو جاتا ہے۔ حل یہ ہے کہ براہ راست Django ORM کے ذریعے راز کو دوبارہ تخلیق کیا جائے:
# Run through the authentik container's shell
cat > /tmp/regen_secret.py << 'EOF'
from authentik.providers.oauth2.models import OAuth2Provider
import secrets
provider = OAuth2Provider.objects.get(name="ECOSIRE Web OAuth2")
new_secret = secrets.token_urlsafe(64)
provider.client_secret = new_secret
provider.save()
print(f"New secret: {new_secret}")
EOF
docker exec -i authentik-worker /lifecycle/ak shell < /tmp/regen_secret.py
پھر اپنے .env.local میں AUTHENTIK_CLIENT_SECRET کو اپ ڈیٹ کریں۔
پیداوار کے ماحول کے متغیرات
# Authentik
AUTHENTIK_URL=https://auth.ecosire.com
AUTHENTIK_INTERNAL_URL=http://localhost:9000 # Server-to-server
AUTHENTIK_CLIENT_ID=ecosire-web
AUTHENTIK_CLIENT_SECRET=your-generated-secret
# App URLs
API_URL=https://api.ecosire.com/api
FRONTEND_URL=https://ecosire.com
# Cookie security
COOKIE_SECRET=random-32-char-string-for-signing
NODE_ENV=production
اکثر پوچھے گئے سوالات
NextAuth.js کے بجائے Authentik کیوں استعمال کریں؟
NextAuth.js سادہ ایپلیکیشنز کے لیے ایک بہترین انتخاب ہے، لیکن یہ آپ کے Next.js ایپ کے لیے تصنیف کرتا ہے۔ Authentik ایک اسٹینڈ تنہا شناخت فراہم کنندہ ہے جو کسی بھی فریم ورک — NestJS، موبائل ایپس، تھرڈ پارٹی ٹولز کے ساتھ کام کرتا ہے۔ اگر آپ کو متعدد ایپلیکیشنز میں SSO کی ضرورت ہے، SAML انٹرپرائز لاگ ان کو سپورٹ کرنا چاہتے ہیں، یا اپنی ایپ سے الگ صارفین اور گروپس کے انتظام کے لیے UI کی ضرورت ہے، تو Authentik بہتر انتخاب ہے۔
OIDC اور OAuth2 میں کیا فرق ہے؟
OAuth2 ایک اجازت کا فریم ورک ہے — یہ وضاحت کرتا ہے کہ اسناد کا اشتراک کیے بغیر رسائی کیسے دی جائے۔ OIDC (OpenID Connect) OAuth2 کے اوپر بنایا گیا ہے اور تصدیق کا اضافہ کرتا ہے — یہ بتاتا ہے کہ کس طرح تصدیق کی جائے کہ صارف کون ہے۔ Authentik دونوں کی حمایت کرتا ہے۔ آپ کی درخواست لاگ ان کے لیے، آپ OIDC چاہتے ہیں (جو آپ کو صارف کے دعووں کے ساتھ ایک ID ٹوکن دیتا ہے)۔ OAuth2 تنہا فریق ثالث کی اجازت کے منظرناموں کے لیے ہے جیسے "اس ایپ کو میری Google Drive تک رسائی کی اجازت دیں۔"
میں ٹوکن ریفریش کو کیسے ہینڈل کروں؟
ریفریش ٹوکن کو HttpOnly کوکی میں ایک محدود راستے کے ساتھ اسٹور کریں (جیسے، /auth/refresh)۔ رسائی ٹوکن کی میعاد ختم ہونے پر، آپ کا API 401 لوٹاتا ہے، اور آپ کا فرنٹ اینڈ ریفریش ٹوکن کا استعمال کرتے ہوئے نیا رسائی ٹوکن حاصل کرنے کے لیے /auth/refresh کو کال کرتا ہے۔ ریفریش اینڈ پوائنٹ نئے ٹوکنز کے لیے Authentik کے ساتھ ریفریش ٹوکن کا تبادلہ کرتا ہے اور نئی کوکیز سیٹ کرتا ہے۔ ریفریش کے بعد خودکار دوبارہ کوشش کے ساتھ اپنے API کلائنٹ میں 401 کو ہینڈل کریں۔
کیا Authentik انٹرپرائز SAML فراہم کنندگان کو سنبھال سکتا ہے؟
ہاں — Authentik SAML 2.0 کو سروس فراہم کرنے والے اور شناخت فراہم کرنے والے دونوں کے طور پر سپورٹ کرتا ہے۔ Okta، Azure AD، یا Ping Identity استعمال کرنے والے انٹرپرائز صارفین کے لیے، آپ SAML فیڈریشن کو کنفیگر کر سکتے ہیں تاکہ صارف اپنے کارپوریٹ اسناد کے ساتھ لاگ ان ہوں۔ Authentik SAML دعووں کا OIDC ٹوکنز میں ترجمہ کرتا ہے، لہذا آپ کے ایپلیکیشن کوڈ کو SAML کو براہ راست ہینڈل کرنے کی ضرورت نہیں ہے۔
میں مقامی طور پر auth بہاؤ کی جانچ کیسے کروں؟
اپنی درخواست کے ساتھ مقامی طور پر ڈوکر کمپوز کے ساتھ Authentik چلائیں۔ ری ڈائریکٹ URIs کو http://localhost:3000/auth/callback شامل کرنے کے لیے ترتیب دیں۔ مقامی صارف کے ساتھ Authentik کا ٹیسٹ موڈ استعمال کریں۔ ایکسچینج کوڈ کے بہاؤ کے لیے، 60 سیکنڈ کا TTL مقامی ترقی کے لیے کافی فراخدل ہے۔ اگر آپ کو OIDC کے بہاؤ کو ڈیبگ کرنے کی ضرورت ہے، تو Authentik کا ایڈمن پینل ایونٹس لاگ میں ٹوکن کے تبادلے کی تمام کوششیں دکھاتا ہے۔
اگلے اقدامات
کسی بھی ایپلیکیشن کے لیے ایک محفوظ، پروڈکشن کے لیے تیار تصدیقی نظام کو نافذ کرنا انجینئرنگ کے سب سے اہم چیلنجز میں سے ایک ہے۔ ECOSIRE HttpOnly کوکی پر مبنی توثیق، ایک بار کے تبادلے کے کوڈز، اور NestJS اور Next.js کے درمیان مکمل OIDC انضمام کے ساتھ، متعدد ایپلی کیشنز کے لیے پروڈکشن سرونگ SSO میں Authentik چلاتا ہے۔
چاہے آپ کو auth سسٹم آرکیٹیکچر، Authentik کی تعیناتی، یا ایک مکمل انٹرپرائز پلیٹ فارم کی ضرورت ہو، یہ دیکھنے کے لیے کہ ہم کس طرح مدد کر سکتے ہیں ہماری ترقیاتی خدمات کو دریافت کریں۔
تحریر
ECOSIRE Research and Development Team
ECOSIRE میں انٹرپرائز گریڈ ڈیجیٹل مصنوعات بنانا۔ Odoo انٹیگریشنز، ای کامرس آٹومیشن، اور AI سے چلنے والے کاروباری حل پر بصیرت شیئر کرنا۔
متعلقہ مضامین
JWT Authentication: Security Best Practices in 2026
Secure your APIs with JWT best practices: RS256 vs HS256, HttpOnly cookies, token rotation, refresh patterns, and common vulnerabilities to avoid in 2026.
API Gateway Patterns and Best Practices for Modern Applications
Implement API gateway patterns including rate limiting, authentication, request routing, circuit breakers, and API versioning for scalable web architectures.
Zero Trust Architecture Implementation: A Practical Guide for Businesses
Implement zero trust architecture with practical steps covering identity verification, network segmentation, device trust, and continuous monitoring.