यह पृष्ठ अंग्रेज़ी से अनुवादित है। अंग्रेज़ी पृष्ठ पढ़ें
कौन कॉल कर रहा है, और ऐप को इसका कितना भरोसा है। Zeyn में अंदर आने के चार तरीके हैं:
| तरीका | किसके लिए इस्तेमाल करें | Cargo feature |
|---|---|---|
ForwardAuth | ऐप के सामने का सिंगल साइन ऑन प्रॉक्सी हेडर सेट करता है | डिफ़ॉल्ट |
JwtAuth | Authorization: Bearer <JWT> वाले API, MCP और सेवा कॉल | oidc |
OidcAuth | ब्राउज़र उपयोगकर्ता OpenID Connect प्रोवाइडर पर साइन इन करते हैं | oidc |
Totp | उन ऐप के लिए दूसरा फ़ैक्टर जिनके साइन इन में कोई नहीं है | totp |
App::accounts | ऐप अपने उपयोगकर्ता रखता है: पासवर्ड, दो फ़ैक्टर, पासकी (docs/accounts.md) | accounts |
RequireMfa (हमेशा उपलब्ध) feature, Screen या action पर दूसरा फ़ैक्टर माँगता है। डिफ़ॉल्ट बिल्ड में oidc और totp में से कुछ नहीं है, इसलिए जो ऐप इन्हें इस्तेमाल नहीं करते वे कुछ नहीं चुकाते।
zeyn = { version = "0.0.2", features = ["oidc"] }परखे गए प्रोवाइडर Keycloak और Authentik हैं। जो भी प्रोवाइडर OpenID Connect discovery, PKCE के साथ authorization code flow और JWKS का पालन करता है, उसे काम करना चाहिए।
API और MCP के लिए Bearer टोकन
use zeyn::{ClaimMap, JwtAuth};
App::new("Stock").auth(
JwtAuth::new("https://sso.example.com/realms/main", "stock-api")
.claims(
ClaimMap::new()
.roles("realm_access.roles")
.role("stock-admins", ["stock.product.read", "stock.product.write"]),
),
)- पहला आर्ग्युमेंट issuer है ठीक वैसा जैसा टोकन के
issमें दिखता है। हस्ताक्षर कुंजियाँ<issuer>/.well-known/openid-configurationमें नामित JWKS से आती हैं; जब issuer के पास discovery दस्तावेज़ न हो तोjwks_urlपता तय करता है। - दूसरा आर्ग्युमेंट वह audience है जिसे टोकन को
audमें नाम देना होगा। Keycloak में, उस क्लाइंट में जो API के लिए टोकन माँगता है एक "Audience" मैपर जोड़ें। - जब तक
algorithms([...])अधिक न कहे (RS384,RS512,PS256,PS384,PS512,ES256,ES384,EdDSA) केवलRS256स्वीकार है।noneऔर हरHS*एल्गोरिद्म हमेशा अस्वीकार हैं: उन्हें अनुमति देने का कोई तरीका नहीं है। कुंजी केवल अपने प्रकार के एल्गोरिद्म परिवार के लिए इस्तेमाल होती है, इसलिए सार्वजनिक कुंजी पर HMAC से "हस्ताक्षरित" टोकन अस्वीकार होता है। expआवश्यक है।exp,nbfऔरiatको एक मिनट की छूट मिलती है (leeway(...), अधिकतम पाँच मिनट)।- कुंजियाँ एक घंटे कैश रहती हैं (
key_ttl)। जो टोकन अज्ञातkidनाम दे वह कुंजियाँ फिर लाता है, हर 30 सेकंड में अधिकतम एक बार (min_key_refresh), इसलिए गढ़े गए कुंजी आईडी वाले टोकन प्रोवाइडर पर बाढ़ नहीं ला सकते। अधिकतम 16 कुंजियाँ रखी जाती हैं और 256 KB से बड़े उत्तर अस्वीकार होते हैं। विफल fetch पुरानी कुंजियाँ रखता है। - अस्वीकार किया गया टोकन बस साइन इन नहीं होता; कारण केवल debug स्तर पर लॉग होता है।
टेनेंट
हर ऑथेंटिकेटर Identity के वही दो फ़ील्ड भरता है: tenant (उपयोगकर्ता का अपना टेनेंट) और tenants (हर अन्य टेनेंट जिसमें उपयोगकर्ता काम कर सकता है)। Identity::member_of(id) दोनों के लिए सही है; सुपरयूज़र (DevAuth) हर टेनेंट का है।
| ऑथेंटिकेटर | टेनेंट कहाँ से आते हैं |
|---|---|
JwtAuth, OidcAuth | ClaimMap::tenant से नामित claim, स्ट्रिंग या सूची |
accounts | उपयोगकर्ता के tenant प्रकार के claim (Claim::new("tenant", "acme")), जोड़े जाने के क्रम में |
ForwardAuth | ForwardAuth::tenant से नामित हेडर, एक टेनेंट |
आपका अपना Authenticator | identity.tenant और identity.tenants सेट करें |
App::tenancy(resolver) के साथ अनुरोध का टेनेंट अनुरोध (होस्ट या हेडर) से आता है। साइन इन उपयोगकर्ता को उसका सदस्य होना चाहिए; वरना अनुरोध साइन आउट की तरह चलता है, इसलिए सार्वजनिक Screen अब भी काम करती हैं और बाकी सब अनाम उपयोगकर्ता की तरह उत्तर देता है। बिना किसी टेनेंट का साइन इन उपयोगकर्ता किसी का नहीं है। जब resolver को कोई टेनेंट न मिले, तो उपयोगकर्ता का अपना टेनेंट लागू होता है।
Claim
ClaimMap claim को Identity में बदलता है:
| मेथड | डिफ़ॉल्ट | टिप्पणी |
|---|---|---|
user("email") | preferred_username, वरना sub | इसके बिना टोकन अस्वीकार होता है |
tenant("tenant") | कोई नहीं | उपयोगकर्ता का टेनेंट; सूची (ऐरे या खाली जगह से अलग) हर वह टेनेंट बताती है जिसका उपयोगकर्ता है, पहला उपयोगकर्ता का अपना है। नीचे "टेनेंट" देखें |
roles("realm_access.roles") | कोई नहीं | बिंदु वाला पथ; Keycloak realm roles, या Authentik में groups |
role("clerks", [...]) | कोई नहीं | भूमिका धारकों या समूह के सदस्यों के लिए अनुमतियाँ |
permissions("scope") | कोई नहीं | वह claim जिसके मान अनुमतियाँ हैं; सूची या खाली जगह से अलग स्ट्रिंग |
time_zone("zoneinfo") | zoneinfo | उपयोगकर्ता का क्षेत्र |
mfa_acr(["2"]) | कोई नहीं | acr मान जिनका अर्थ दूसरा फ़ैक्टर है; नीचे देखें |
OidcAuth ID टोकन के claim पढ़ता है। Keycloak realm roles केवल access टोकन में रखता है जब तक क्लाइंट के "realm roles" मैपर पर "Add to ID token" चालू न हो; उसे चालू करें (या id.token.claim के साथ oidc-usermodel-realm-role-mapper जोड़ें)। Authentik profile स्कोप के साथ groups को ID टोकन में रखता है।
auth_time (वरना iat) Identity::authenticated_at बन जाता है, जिसे reauth action पढ़ती हैं।
ब्राउज़र साइन इन
use zeyn::{ClaimMap, OidcAuth};
let oidc = OidcAuth::new(
"https://sso.example.com/realms/main",
"stock-web",
"https://stock.example.com",
config.secret("auth.session_key")?,
)
.client_secret(config.secret("auth.client_secret")?)
.claims(ClaimMap::new().roles("realm_access.roles").role("stock-admins", ["stock.product.write"]));
App::new("Stock").oidc(oidc)App::oidc इसे ऑथेंटिकेटर, login_url, निष्क्रियता साइन आउट पता और पुनः प्रमाणीकरण पता बनाता है। यह तीन रूट जोड़ता है:
| रूट | क्या करता है |
|---|---|
GET /_zeyn/auth/login?return_to=/path | साइन इन शुरू करता है और प्रोवाइडर पर रीडायरेक्ट करता है |
GET /_zeyn/auth/callback | प्रोवाइडर का वापसी रीडायरेक्ट |
GET /_zeyn/auth/logout | यहाँ और प्रोवाइडर पर सत्र खत्म करता है |
प्रोवाइडर पर <public url>/_zeyn/auth/callback को redirect URI और <public url>/ को post logout redirect URI के रूप में पंजीकृत करें। सार्वजनिक URL कॉन्फ़िगर किया जाता है, अनुरोध के Host हेडर से कभी नहीं लिया जाता। बेस या सार्वजनिक पथ के साथ रूट उसके नीचे चले जाते हैं।
प्रवाह
- साइन आउट उपयोगकर्ता ऐसा पृष्ठ खोलता है जिसे अनुमति चाहिए और
/_zeyn/auth/loginपर जाता है। - सर्वर नया
state,nonceऔर PKCE verifier बनाता है (प्रत्येक 32 यादृच्छिक बाइट), उन्हेंreturn_toपथ के साथ दस मिनट जीने वाली कुकी में seal करता है, औरcode_challenge_method=S256के साथ प्रोवाइडर के authorization endpoint पर रीडायरेक्ट करता है। - प्रोवाइडर उपयोगकर्ता को कोड और
stateके साथ वापस भेजता है। सर्वर कुकी खोलता है,stateकी तुलना constant time में करता है, कुकी हटाता है, और PKCE verifier के साथ कोड का आदान-प्रदान करता है (सेट होने पर क्लाइंट सीक्रेट HTTP basic ऑथेंटिकेशन से)। - ID टोकन का हस्ताक्षर प्रोवाइडर के JWKS से जाँचा जाता है, फिर
iss,aud, कई audience होने परazp,exp,nbf,iatऔरnonce। - सत्र कुकी सेट होती है और उपयोगकर्ता
return_toपर लौटता है। वहाँ केवल स्थानीय पथ स्वीकार है; बाकी सब ऐप के शुरुआती पृष्ठ पर जाता है।
सत्र कुकी
सर्वर पर कोई सत्र स्टोर नहीं है। सत्र एक कुकी zeyn_session में रहता है:
- AES-256-GCM से एन्क्रिप्टेड और प्रमाणित। कुंजी सत्र कुंजी से व्युत्पन्न होती है (
Config::secret, कम से कम 32 बाइट; छोटी कुंजी ऐप को स्टार्टअप पर रोक देती है)। कुकी का नाम साथ बँधा है, इसलिए एक कुकी के लिए seal किया मान दूसरी के रूप में कभी नहीं खुलता। बदला हुआ एक बाइट, या दूसरी कुंजी से seal की गई कुकी, बस साइन इन नहीं होती। - इसमें उपयोगकर्ता, टेनेंट, समय क्षेत्र,
ClaimMapद्वारा मैप की गई भूमिकाएँ, प्रत्यक्ष अनुमतियाँ, साइन इन का समय, क्या दूसरा फ़ैक्टर इस्तेमाल हुआ, सत्र का अंत और, रिफ़्रेश टोकन के साथ, रिफ़्रेश टोकन होता है। ID टोकन या access टोकन नहीं। HttpOnly,SameSite=Lax(प्रोवाइडर का वापसी रीडायरेक्ट क्रॉस साइट नेविगेशन है) और जब सार्वजनिक URLhttps://हो (याApp::secure_cookies(true)) तबSecure।- जो सत्र 3800 बाइट से बड़ा होगा उसे साइन इन पर लॉग संदेश के साथ अस्वीकार किया जाता है, कभी काटा नहीं जाता। कम भूमिकाएँ मैप करें या रिफ़्रेश टोकन बंद करें।
- यह
session_lifetimeतक चलता है (आठ घंटे जब तक सेट न हो)। सत्र कुंजी बदलना सबको साइन आउट कर देता है।
CSRF सुरक्षा नहीं बदलती: रनटाइम के हर POST को अब भी x-zeyn-csrf हेडर में zeyn_csrf टोकन चाहिए, कोई भी कुकी उपयोगकर्ता को साइन इन करे।
रिफ़्रेश टोकन
refresh_tokens(true) रिफ़्रेश टोकन को कुकी में रखता है। जब टोकन समाप्त होते हैं, अगला अनुरोध चलने से पहले उन्हें रिफ़्रेश करता है और नवीनीकृत कुकी वापस भेजता है। अस्वीकार किया गया रिफ़्रेश (उपयोगकर्ता प्रोवाइडर पर निष्क्रिय या साइन आउट हुआ) सत्र खत्म कर देता है। रिफ़्रेश टोकन के बिना सत्र session_lifetime तक चलता है, प्रोवाइडर पर जो भी हो। रिफ़्रेश पूरे ऐप पर एक परत के रूप में चलता है, इसलिए हर रूट कवर होता है, वैकल्पिक feature के भी।
यदि प्रोवाइडर रिफ़्रेश टोकन घुमाता है और पुराने को तुरंत रद्द करता है, तो एक ही क्षण पर रिफ़्रेश करने वाले दो अनुरोध उपयोगकर्ता को साइन आउट कर सकते हैं। घुमाव बंद रखें या थोड़े समय के लिए दोबारा इस्तेमाल की अनुमति दें।
लॉगआउट
/_zeyn/auth/logout सत्र कुकी हटाता है और उपयोगकर्ता को client_id और post_logout_redirect_uri के साथ प्रोवाइडर के end_session_endpoint पर भेजता है। उस endpoint के बिना उपयोगकर्ता ऐप के शुरुआती पृष्ठ पर जाता है।
उसी ऐप के API और MCP क्लाइंट
OidcAuth::accept_bearer("stock-api") उसी issuer से उस audience के लिए bearer access टोकन भी स्वीकार करता है, उसी claim मैपिंग के साथ। Authorization: Bearer हेडर वाले अनुरोध को केवल उसके टोकन से परखा जाता है।
दूसरा फ़ैक्टर और step up
Identity::mfa तब सही है जब उपयोगकर्ता ने दूसरा फ़ैक्टर साबित किया:
OidcAuthऔरJwtAuthके साथ, जबamrmfa,otp,hwk,swk,sc,sms, बायोमेट्रिक या कोई अन्य दूसरा फ़ैक्टर नाम दे (RFC 8176), या जबacrClaimMap::mfa_acrमें से एक हो;Totpके साथ, सत्यापित कोड के बाद।
RequireMfa इसे माँगता है:
Feature::new("payroll").guard(RequireMfa::new())
App::new("Back office").interceptor(RequireMfa::new().screen("bank_accounts").action("approve_payment"))feature गार्ड के रूप में यह पूरी feature को कवर करता है। इंटरसेप्टर के रूप में यह सूचीबद्ध Screen और action को कवर करता है, या जब दोनों सूचियाँ खाली हों तो हर कॉल को।
जब फ़ैक्टर छूटा हो, पृष्ठ लोड return_to के साथ step up पते पर रीडायरेक्ट होता है। अन्य कॉल कोड mfa_required के साथ 401 देती हैं (API पर problem type urn:zeyn:problem:mfa_required); रनटाइम step up पृष्ठ नए टैब में खोलता है और उपयोगकर्ता के लौटने पर अनुरोध दोहराता है, जैसा reauth_required के लिए करता है।
OidcAuth के साथ step up पता /_zeyn/auth/login?mfa=1 है। यह prompt=login और max_age=0 के साथ acr_values (mfa_acr मान) भेजता है। यदि नया ID टोकन अब भी कोई दूसरा फ़ैक्टर नहीं दिखाता, तो callback चक्कर लगाने के बजाय उसे अस्वीकार करता है।
- Keycloak: ब्राउज़र फ़्लो को प्रमाणीकरण स्तर दें (स्तर 2 पर सशर्त OTP या WebAuthn चरण) और
mfa_acr(["2"])इस्तेमाल करें। Keycloakacrका उत्तर पहुँचे स्तर से देता है, या जब क्लाइंट नामों को स्तरों से मैप करे (acr.loa.map) तो उसके नाम से; तब उन नामों को सूचीबद्ध करें। - Authentik: Authentik
amrके ज़रिए बताता है कि उपयोगकर्ता ने कैसे साइन इन किया, जब फ़्लो ने उन चरणों का उपयोग किया। ऐप्लिकेशन के लिए authenticator validation चरण वाला फ़्लो इस्तेमाल करें।
OidcAuth के तहत reauth action /_zeyn/auth/login?reauth=1 पर जाती हैं, जो prompt=login और max_age=0 भेजता है, इसलिए नया auth_time ताज़ा होता है।
बिल्ट इन TOTP
उन ऐप के लिए जिनके साइन इन में दूसरा फ़ैक्टर नहीं है (बिना MFA वाला प्रॉक्सी, ऐप-विशिष्ट ऑथेंटिकेटर):
use zeyn::{Totp, TotpStore};
App::new("Back office")
.auth(ForwardAuth::new("x-forwarded-user"))
.totp(Totp::new(TotpTable::new(pool), config.secret("auth.totp_key")?).issuer("Back office"))/_zeyn/auth/totpबिना दूसरे फ़ैक्टर वाले उपयोगकर्ता को नामांकन दिखाता है: सर्वर पर SVG के रूप में बना QR कोड (कोई स्क्रिप्ट नहीं, कोई छवि अनुरोध नहीं), हाथ से टाइप करने की कुंजी, और कोड फ़ील्ड। सीक्रेट एक seal की गई कुकी में दस मिनट इंतज़ार करता है जब तक कोई कोड साबित न करे कि ऐप के पास वह है।- नामांकन के बाद उपयोगकर्ता एक बार दस रिकवरी कोड देखता है। स्टोर केवल उनके keyed हैश रखता है (TOTP कुंजी से व्युत्पन्न कुंजी के साथ HMAC-SHA256), इसलिए अकेले स्टोर की प्रति का ऑफ़लाइन अंदाज़ा नहीं लगाया जा सकता। हर एक एक बार काम करता है।
- सहेजा गया सीक्रेट TOTP कुंजी से seal किया जाता है और उपयोगकर्ता से बँधा होता है, इसलिए अकेला स्टोर कोड नहीं बना सकता और रिकॉर्ड उपयोगकर्ताओं के बीच बदले नहीं जा सकते।
- नामांकित उपयोगकर्ता वहाँ कोड या रिकवरी कोड से सत्यापित करते हैं। कोड एक बार स्वीकार होता है (आखिरी उपयोग किया time step सहेजा जाता है) और दोनों ओर एक step की घड़ी की त्रुटि की छूट है।
- प्रयास प्रति उपयोगकर्ता सीमित हैं (जब तक
attemptsकुछ और न कहे, पाँच मिनट में पाँच) ऐप के रेट स्टोर से, इसलिएRedisStoreके साथ कई सर्वर गिनती साझा करते हैं। - सत्यापित कोड
lifetime(जब तक सेट न हो बारह घंटे) के लिए seal की गईzeyn_mfaकुकी सेट करता है जो आइडेंटिटी कोmfaसे चिह्नित करती है औरreauthaction के लिए ताज़ा साइन इन गिनी जाती है। - नामांकित फ़ैक्टर अकेले सत्र से बदला नहीं जा सकता; उसे रीसेट करना स्टोर में प्रशासक का काम है।
- फ़ॉर्म उसी origin पर फ़ॉर्म फ़ील्ड के रूप में
zeyn_csrfटोकन के साथ पोस्ट करते हैं।
TotpStore दो async मेथड है, load(user) और save(user, record); MemoryTotpStore टेस्ट के लिए है। TOTP कुंजी स्थिर रखें: दूसरी कुंजी से seal हुए रिकॉर्ड अब नहीं खुलते।
टेस्टिंग
crates/zeyn/tests/oidc.rs हर फ़्लो को टेस्ट में एक छोटे issuer के विरुद्ध चलाता है जो रन के लिए बनाई कुंजी से हस्ताक्षर करता है, और जाँचता है कि समाप्त टोकन, गलत audience और issuer, alg=none, सार्वजनिक कुंजी से keyed HMAC टोकन, बदले claim, छेड़ी गई कुकी, दोबारा इस्तेमाल हुआ या बेमेल state और गलत nonce अस्वीकार होते हैं। crates/zeyn/tests/totp.rs नामांकन, सत्यापन, replay, रिकवरी कोड और दर सीमा कवर करता है।
डेवलपमेंट मोड में Keycloak के साथ वही फ़्लो उसके विरुद्ध भी चलते हैं:
docker run --rm -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
ZEYN_TEST_OIDC=http://127.0.0.1:8080 cargo test -p zeyn --features oidc --test oidcटेस्ट अपने क्लाइंट और उपयोगकर्ता के साथ zeyn-test realm बनाता है और अंत में उसे हटा देता है। ZEYN_TEST_OIDC के बिना यह छोड़ा जाता है।