प्रामाणिक ओआईडीसी/एसएसओ: पूर्ण एकीकरण गाइड
ऑथेंटिक एक ओपन-सोर्स पहचान प्रदाता है जो आपको ओक्टा या ऑथ0 की जटिलता (या लागत) के बिना एंटरप्राइज़-ग्रेड एसएसओ, ओआईडीसी, एसएएमएल और ओएथ2 प्रदान करता है। स्व-होस्ट किए गए अनुप्रयोगों के लिए, यह ऑथेंटिक के ओआईडीसी एंडपॉइंट, उपयोगकर्ता प्रबंधन, समूह-आधारित पहुंच नियंत्रण और उद्यम नीतियां प्रदान करता है - यह सब आपके अपने बुनियादी ढांचे के तहत।
यह मार्गदर्शिका Next.js 16 और NestJS 11 के साथ उत्पादन प्रामाणिक एकीकरण को कवर करती है: OAuth2 प्रदाता कॉन्फ़िगरेशन, सुरक्षित प्राधिकरण कोड प्रवाह, एक बार विनिमय कोड पैटर्न, JWT सत्यापन, और सूक्ष्म मुद्दे जो एक प्रमाणीकरण कार्यान्वयन को बनाते या तोड़ते हैं।
मुख्य बातें
- प्रामाणिक टोकन HttpOnly कुकीज़ में रहने चाहिए - कभी भी लोकलस्टोरेज या क्वेरी पैरामीटर में नहीं
- कॉलबैक से फ्रंटएंड तक टोकन पास करने के लिए वन-टाइम एक्सचेंज कोड (60-सेकंड टीटीएल) का उपयोग करें
- सर्वर-टू-सर्वर टोकन एक्सचेंज के लिए हमेशा
AUTHENTIK_INTERNAL_URLका उपयोग करें (नेटवर्क हॉप्स से बचें)- कैश से हटाने से पहले एक्सचेंज कोड की समाप्ति की जांच होनी चाहिए (दौड़ की स्थिति की रोकथाम)
- एपीआई के माध्यम से पुनर्जीवित किए गए प्रामाणिक क्लाइंट रहस्यों में हैशिंग बेमेल हो सकता है - सीधे Django ORM का उपयोग करें
- JWT दावों में
organizationIdशामिल होना चाहिए - इसे प्रामाणिक संपत्ति मानचित्रण में एन्कोड करें- कुकीज़ से JWT निष्कर्षण से पहले NestJS में
cookie-parserमिडलवेयर आवश्यक है- ओआईडीसी डिस्कवरी एंडपॉइंट सभी टोकन/यूजरइन्फो यूआरएल प्रदान करता है - उन्हें हार्डकोड न करें
प्रामाणिक विन्यास
OAuth2 प्रदाता बनाना
प्रामाणिक व्यवस्थापक पैनल में (/if/admin/):
- एप्लिकेशन > प्रदाता > बनाएं पर जाएं
- OAuth2/OpenID प्रदाता चुनें
- कॉन्फ़िगर करें:
- नाम:
ECOSIRE Web OAuth2 - प्राधिकरण प्रवाह:
default-provider-authorization-implicit-consent - ग्राहक प्रकार:
Confidential - क्लाइंट आईडी:
ecosire-web(आप इसे चुनें) - यूआरआई को पुनर्निर्देशित करें:
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 प्रदाता की "स्कोप्स" सूची में जोड़ें और अपने एप्लिकेशन द्वारा अनुरोधित स्कोप्स में organization शामिल करें।
बैकएंड: NestJS प्रामाणिक मॉड्यूल
जेडब्ल्यूटी रणनीति
// 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 सरल अनुप्रयोगों के लिए एक बढ़िया विकल्प है, लेकिन यह आपके Next.js ऐप के साथ प्रमाणीकरण जोड़ता है। ऑथेंटिक एक स्टैंडअलोन पहचान प्रदाता है जो किसी भी ढांचे - नेस्टजेएस, मोबाइल ऐप, थर्ड-पार्टी टूल के साथ काम करता है। यदि आपको कई अनुप्रयोगों में एसएसओ की आवश्यकता है, एसएएमएल एंटरप्राइज़ लॉगिन का समर्थन करना चाहते हैं, या अपने ऐप से अलग उपयोगकर्ताओं और समूहों को प्रबंधित करने के लिए यूआई की आवश्यकता है, तो ऑथेंटिक बेहतर विकल्प है।
OIDC और OAuth2 के बीच क्या अंतर है?
OAuth2 एक प्राधिकरण ढांचा है - यह परिभाषित करता है कि क्रेडेंशियल साझा किए बिना पहुंच कैसे प्रदान की जाए। OIDC (ओपनआईडी कनेक्ट) OAuth2 के शीर्ष पर बनाया गया है और प्रमाणीकरण जोड़ता है - यह निर्दिष्ट करता है कि उपयोगकर्ता कौन है, इसकी पुष्टि कैसे की जाए। ऑथेंटिक दोनों का समर्थन करता है। अपने एप्लिकेशन लॉगिन के लिए, आप ओआईडीसी चाहते हैं (जो आपको उपयोगकर्ता दावों के साथ एक आईडी टोकन देता है)। OAuth2 अकेले तृतीय-पक्ष प्राधिकरण परिदृश्यों के लिए है जैसे "इस ऐप को मेरे Google ड्राइव तक पहुंचने की अनुमति दें।"
मैं टोकन रीफ्रेश को कैसे प्रबंधित करूं?
रिफ्रेश टोकन को प्रतिबंधित पथ (उदाहरण के लिए, /auth/refresh) के साथ HttpOnly कुकी में संग्रहीत करें। जब एक्सेस टोकन समाप्त हो जाता है, तो आपका एपीआई 401 लौटाता है, और आपका फ्रंटएंड रिफ्रेश टोकन का उपयोग करके नया एक्सेस टोकन प्राप्त करने के लिए /auth/refresh पर कॉल करता है। रिफ्रेश एंडपॉइंट नए टोकन के लिए ऑथेंटिक के साथ रिफ्रेश टोकन का आदान-प्रदान करता है और नई कुकीज़ सेट करता है। रीफ्रेश के बाद स्वचालित पुनः प्रयास के साथ अपने एपीआई क्लाइंट में 401 को संभालें।
क्या ऑथेंटिक एंटरप्राइज़ एसएएमएल प्रदाताओं को संभाल सकता है?
हाँ - ऑथेंटिक एक सेवा प्रदाता और पहचान प्रदाता दोनों के रूप में SAML 2.0 का समर्थन करता है। ओक्टा, एज़्योर एडी, या पिंग आइडेंटिटी का उपयोग करने वाले एंटरप्राइज़ ग्राहकों के लिए, आप एसएएमएल फ़ेडरेशन को कॉन्फ़िगर कर सकते हैं ताकि उपयोगकर्ता अपने कॉर्पोरेट क्रेडेंशियल के साथ लॉग इन कर सकें। ऑथेंटिक SAML अभिकथनों को OIDC टोकन में अनुवादित करता है, इसलिए आपके एप्लिकेशन कोड को सीधे SAML को संभालने की आवश्यकता नहीं होती है।
मैं स्थानीय स्तर पर प्रमाणीकरण प्रवाह का परीक्षण कैसे करूं?
अपने एप्लिकेशन के साथ स्थानीय रूप से डॉकर कंपोज़ के साथ ऑथेंटिक चलाएँ। http://localhost:3000/auth/callback को शामिल करने के लिए रीडायरेक्ट URI कॉन्फ़िगर करें। स्थानीय उपयोगकर्ता के साथ ऑथेंटिक के परीक्षण मोड का उपयोग करें। एक्सचेंज कोड प्रवाह के लिए, 60-सेकंड टीटीएल स्थानीय विकास के लिए काफी उदार है। यदि आपको ओआईडीसी प्रवाह को डीबग करने की आवश्यकता है, तो ऑथेंटिक का व्यवस्थापक पैनल इवेंट लॉग में सभी टोकन विनिमय प्रयासों को दिखाता है।
अगले चरण
एक सुरक्षित, उत्पादन-तैयार प्रमाणीकरण प्रणाली को लागू करना किसी भी एप्लिकेशन के लिए सबसे महत्वपूर्ण इंजीनियरिंग चुनौतियों में से एक है। ECOSIRE HttpOnly कुकी-आधारित ऑथ, वन-टाइम एक्सचेंज कोड और NestJS और Next.js के बीच पूर्ण OIDC एकीकरण के साथ कई अनुप्रयोगों के लिए SSO के उत्पादन में ऑथेंटिक चलाता है।
चाहे आपको ऑथेंटिक सिस्टम आर्किटेक्चर, ऑथेंटिक परिनियोजन, या एक संपूर्ण एंटरप्राइज़ प्लेटफ़ॉर्म की आवश्यकता हो, हमारी विकास सेवाओं का पता लगाएं यह देखने के लिए कि हम कैसे मदद कर सकते हैं।
लेखक
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.