मुख्य सामग्री पर जाएँ

प्रमाणीकरण

यह पृष्ठ अंग्रेज़ी से अनुवादित है। अंग्रेज़ी पृष्ठ पढ़ें

कौन कॉल कर रहा है, और ऐप को इसका कितना भरोसा है। Zeyn में अंदर आने के चार तरीके हैं:

तरीकाकिसके लिए इस्तेमाल करेंCargo feature
ForwardAuthऐप के सामने का सिंगल साइन ऑन प्रॉक्सी हेडर सेट करता हैडिफ़ॉल्ट
JwtAuthAuthorization: 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, OidcAuthClaimMap::tenant से नामित claim, स्ट्रिंग या सूची
accountsउपयोगकर्ता के tenant प्रकार के claim (Claim::new("tenant", "acme")), जोड़े जाने के क्रम में
ForwardAuthForwardAuth::tenant से नामित हेडर, एक टेनेंट
आपका अपना Authenticatoridentity.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 हेडर से कभी नहीं लिया जाता। बेस या सार्वजनिक पथ के साथ रूट उसके नीचे चले जाते हैं।

प्रवाह

  1. साइन आउट उपयोगकर्ता ऐसा पृष्ठ खोलता है जिसे अनुमति चाहिए और /_zeyn/auth/login पर जाता है।
  2. सर्वर नया state, nonce और PKCE verifier बनाता है (प्रत्येक 32 यादृच्छिक बाइट), उन्हें return_to पथ के साथ दस मिनट जीने वाली कुकी में seal करता है, और code_challenge_method=S256 के साथ प्रोवाइडर के authorization endpoint पर रीडायरेक्ट करता है।
  3. प्रोवाइडर उपयोगकर्ता को कोड और state के साथ वापस भेजता है। सर्वर कुकी खोलता है, state की तुलना constant time में करता है, कुकी हटाता है, और PKCE verifier के साथ कोड का आदान-प्रदान करता है (सेट होने पर क्लाइंट सीक्रेट HTTP basic ऑथेंटिकेशन से)।
  4. ID टोकन का हस्ताक्षर प्रोवाइडर के JWKS से जाँचा जाता है, फिर iss, aud, कई audience होने पर azp, exp, nbf, iat और nonce।
  5. सत्र कुकी सेट होती है और उपयोगकर्ता return_to पर लौटता है। वहाँ केवल स्थानीय पथ स्वीकार है; बाकी सब ऐप के शुरुआती पृष्ठ पर जाता है।

सर्वर पर कोई सत्र स्टोर नहीं है। सत्र एक कुकी zeyn_session में रहता है:

  • AES-256-GCM से एन्क्रिप्टेड और प्रमाणित। कुंजी सत्र कुंजी से व्युत्पन्न होती है (Config::secret, कम से कम 32 बाइट; छोटी कुंजी ऐप को स्टार्टअप पर रोक देती है)। कुकी का नाम साथ बँधा है, इसलिए एक कुकी के लिए seal किया मान दूसरी के रूप में कभी नहीं खुलता। बदला हुआ एक बाइट, या दूसरी कुंजी से seal की गई कुकी, बस साइन इन नहीं होती।
  • इसमें उपयोगकर्ता, टेनेंट, समय क्षेत्र, ClaimMap द्वारा मैप की गई भूमिकाएँ, प्रत्यक्ष अनुमतियाँ, साइन इन का समय, क्या दूसरा फ़ैक्टर इस्तेमाल हुआ, सत्र का अंत और, रिफ़्रेश टोकन के साथ, रिफ़्रेश टोकन होता है। ID टोकन या access टोकन नहीं।
  • HttpOnly, SameSite=Lax (प्रोवाइडर का वापसी रीडायरेक्ट क्रॉस साइट नेविगेशन है) और जब सार्वजनिक URL https:// हो (या 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 के साथ, जब amr mfa, otp, hwk, swk, sc, sms, बायोमेट्रिक या कोई अन्य दूसरा फ़ैक्टर नाम दे (RFC 8176), या जब acr ClaimMap::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"]) इस्तेमाल करें। Keycloak acr का उत्तर पहुँचे स्तर से देता है, या जब क्लाइंट नामों को स्तरों से मैप करे (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 से चिह्नित करती है और reauth action के लिए ताज़ा साइन इन गिनी जाती है।
  • नामांकित फ़ैक्टर अकेले सत्र से बदला नहीं जा सकता; उसे रीसेट करना स्टोर में प्रशासक का काम है।
  • फ़ॉर्म उसी 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 के बिना यह छोड़ा जाता है।