דילוג לתוכן
תיעוד Nodaro
תיעודמדריך הרכיביםמודליםתבניותסוכני AI (MCP)מפתחיםאירוח עצמימחקרהשוואות

התחברות חיצונית (SSO)

התחברות משתמשים להתקנת Nodaro דרך ספק זהות מהימן, עם assertion חתום בפורמט JWT או עם OIDC ו-SAML, וקביעת האופן שבו חשבונות מקושרים.

התחברות חיצונית (SSO) מאפשרת למשתמשים להתחבר להתקנת Nodaro דרך ספק זהות (IdP) מהימן. באינטגרציה העיקרית, ספק הזהות שלכם חותם על assertion קצר־מועד בפורמט JWT, Nodaro מאמת אותו, והדפדפן מקבל סשן Nodaro רגיל. SSO כבוי עד שמנהל השרת של ההתקנה מגדיר לפחות ספק אחד.

החשבון שהמשתמש מגיע אליו הוא חשבון רגיל. SSO לא מוסיף פרטי גישה מיוחדים: הוא רק דרך להתחיל סשן.

שתי דרכי אינטגרציה

סוגלמי זה מתאיםאיך זה עובד
assertionמנפיקי אסימונים שלא מדברים OIDC או SAML, כגון מארח שמטמיע את Nodaro ויכול רק להנפיק אסימון חתום קצר־מועדספק הזהות שלכם חותם על JWT, ו-Nodaro מאמת אותו ומחליף אותו בסשן. הדרך הזאת עובדת מקצה לקצה.
oidc, samlספקי זהות שמדברים OpenID Connect או SAML סטנדרטייםדף ההתחברות מתחיל התחברות סטנדרטית עם Supabase Auth של ההתקנה עצמה, שמאמת את התשובה של ספק הזהות. לדרך הזאת יש מגבלות; ראו את הסעיף ספקי OIDC ו-SAML.

הגדרת ספקים

הגדירו בשרת את EXTERNAL_SSO_PROVIDERS למערך JSON של ספקים, ישירות בתוך המשתנה או כ-@/path/to/providers.json (תו @ בהתחלה גורם לקריאת הקובץ שבנתיב הזה). ערך פגום עוצר את השרת באתחול, כך שטעות הקלדה לעולם לא תשמיט ספק בשקט ולא תשאיר את ההתחברות מוגדרת רק בחלקה.

[
  {
    "id": "acme-chat",
    "label": "Acme Chat",
    "kind": "assertion",
    "secret": "a-dedicated-32-character-hmac-key-for-nodaro-only",
    "audience": "nodaro",
    "claimMap": { "email": "email", "emailVerified": "email_verified", "subject": "sub" },
    "initiateUrl": "https://chat.example.com/oauth/nodaro",
    "maxLifetimeSeconds": 300
  },
  {
    "id": "keycloak",
    "label": "Acme (Keycloak)",
    "kind": "oidc",
    "supabaseProvider": "keycloak"
  }
]
שדהחל עלמשמעות
idכולםה-slug של הספק, שמשמש בנתיב /v1/sso/:id ונשמר בכל חשבון מקושר. 1–63 תווים: אות קטנה או ספרה בהתחלה, ואחריה אותיות קטנות, ספרות, _ ו--. בלי נקודות. חייב להיות ייחודי.
labelכולםהשם המוצג של הספק.
kindכולםassertion, oidc או saml.
secretassertion, נדרשמפתח ה-HS256 שמאמת את החתימה של ה-assertion, באורך של 16 תווים לפחות. השתמשו בסוד ייעודי, אף פעם לא בסוד שבו ספק הזהות עצמו חותם על סשנים, כך שפריצה ל-Nodaro לא תאפשר לזייף סשנים של ספק הזהות.
audienceassertion, נדרשהערך שה-claim aud של ה-assertion חייב להיות שווה לו.
claimMapassertion, אופציונליבאילו claims נמצאים האימייל, דגל האימות וה-subject. ברירת המחדל היא email, email_verified ו-sub.
initiateUrlassertion, אופציונלילאן כפתור ה-SSO שולח את המשתמש. בלי השדה הזה, לחיצה על הכפתור מחזירה 400 no_assertion.
initiateUrlByHostassertion, אופציונלימיפוי משם מארח בלבד, באותיות קטנות ובלי פורט, לכתובת שמחליפה את initiateUrl עבור המארח הזה. מיועד לפריסה שעונה בכמה שמות מארח.
maxLifetimeSecondsassertion, אופציונליאורך החיים הארוך ביותר (exp פחות iat) ש-Nodaro מקבל. ברירת המחדל היא 300 שניות, והמקסימום הוא 3,600.
domainoidc, samlנבדק באתחול, אבל לא בשימוש. ראו את הסעיף ספקי OIDC ו-SAML.
supabaseProvideroidcנבדק באתחול, אבל לא בשימוש.
  • השדה initiateUrl לא מעביר שום דבר לספק הזהות. אם אתם רוצים שהמשתמש יגיע לדף מסוים אחרי ההתחברות, ספק הזהות שלכם מוסיף &next=<path> כשהוא מפנה אותו בחזרה.
  • המפתחות של initiateUrlByHost נבדקים באתחול: מפתח עם אותיות גדולות או עם פורט עוצר את השרת. המיפוי לעולם לא מתפרסם, וקישור החשבונות לא מושפע מהמארח שדרכו המשתמש הגיע.

החוזה של ה-assertion

ספק הזהות שלכם מנפיק JWT ושולח את הדפדפן אל GET /v1/sso/:provider?assertion=<jwt>. Nodaro מקבל את ה-assertion רק כשכל הכללים מתקיימים:

  • אלגוריתם. ה-JWT חתום עם HS256 ועם ה-secret של הספק.
  • קהל יעד. ה-claim aud שווה ל-audience של הספק.
  • תפוגה. ה-claim exp קיים והוא לא בעבר, עם סובלנות של 5 שניות להפרשי שעונים.
  • אורך חיים. ההפרש exp פחות iat הוא לכל היותר maxLifetimeSeconds, לפי השעון של Nodaro. ערך iat שנמצא יותר מ-5 שניות בעתיד מוגבל לטווח המותר, ו-iat חסר נחשב לרגע הנוכחי.
  • שימוש חד־פעמי. ה-claim jti קיים וייחודי. Nodaro זוכר כל jti עד שחלון התוקף של ה-assertion עצמו עובר, ודוחה אותו בפעם השנייה.
  • אימייל. ה-claim של האימייל קיים.
  • אימייל מאומת. כדי שחשבון ייווצר או יקושר, דגל האימות חייב להיות true. דגל חסר נחשב לאימייל לא מאומת.

כש-assertion נכשל, התגובה היא 401 עם invalid_signature, invalid_claims, expired, too_long_lived, missing_jti, missing_email או assertion_replayed. נקודת הקצה מקבלת עד 20 בקשות ב-60 שניות מכל כתובת IP; מעבר לזה היא מחזירה 429 rate_limit_exceeded עם כותרת Retry-After.

הדוגמה הזאת, מהצד של ספק הזהות, מנפיקה assertion תקף עם הספרייה jose ל-Node.js:

import { SignJWT } from "jose"
import { randomUUID } from "node:crypto"

const secret = new TextEncoder().encode(process.env.NODARO_SSO_SECRET)

const assertion = await new SignJWT({ email: user.email, email_verified: true })
  .setProtectedHeader({ alg: "HS256" })
  .setSubject(user.id)
  .setAudience("nodaro")
  .setIssuedAt()
  .setExpirationTime("2m")
  .setJti(randomUUID())
  .sign(secret)

const url = new URL("https://nodaro.example.com/v1/sso/acme-chat")
url.searchParams.set("assertion", assertion)
url.searchParams.set("next", "/projects")
res.redirect(url.toString())

זרימת ההתחברות

  1. המשתמש לוחץ על כפתור ה-SSO בדף ההתחברות של Nodaro, והדף קורא ל-GET /v1/sso/:provider בלי assertion.
  2. Nodaro מפנה (302) אל ה-initiateUrl של הספק.
  3. ספק הזהות מאמת את המשתמש ושולח את הדפדפן בחזרה אל GET /v1/sso/:provider?assertion=<jwt>, ואפשר להוסיף גם &next=<path>.
  4. Nodaro מאמת את ה-assertion, דוחה שימוש חוזר ומחיל את כללי קישור החשבונות.
  5. Nodaro מפנה אל /sso?sso_token=<one-time token>. הדף הזה מחליף את האסימון החד־פעמי בסשן.
  6. המשתמש מגיע אל /projects, או אל הנתיב שב-next.

ספק זהות, כגון מארח שמטמיע את Nodaro, יכול גם להתחיל בשלב 3: הוא מפנה את הדפדפן לנקודת הקצה של ההחלפה עם assertion חדש. הפרמטר ?redirect= של דף ההתחברות עצמו לא עובר דרך ההפניות של SSO; עובר רק next שספק הזהות הוסיף.

הצגת כפתור ה-SSO בדף ההתחברות

דף ההתחברות מציג כפתור SSO כששני התנאים מתקיימים:

  • פרופיל הממשק של הפריסה כולל את sso ב-auth.methods ומגדיר את auth.ssoLabel. פרופיל בלי ssoLabel מסיר את sso מהשיטות. הטקסט של הכפתור הוא ה-ssoLabel.
  • מוגדר לפחות ספק אחד. הדף בודק את GET /v1/sso/providers.

פרופילי ממשק זמינים במהדורות Business ו-Cloud. ראו את הדף מהדורות ופרופילי ממשק.

פרופיל שה-auth.methods שלו כולל רק את sso מפעיל גם מחסום בצד השרת. כל חשבון מחובר חייב להיות חשבון שנוצר או קושר דרך SSO, וכל סשן אחר נדחה בקריאת ה-API הראשונה שלו עם 403 sso_required. החריג היחיד הוא חשבון החיוב של הפריסה, שמתואר בהמשך. הוספת email לשיטות מכבה את המחסום בכל ההתקנה.

איך חשבונות מקושרים

כש-assertion תקף, Nodaro מוצא את החשבון או יוצר אותו לפי כללים שנבנו כך ש-assertion לעולם לא יכול להשתלט על חשבון קיים רק כי יש לו אותה כתובת אימייל.

מצבתוצאה
אין חשבון עם האימייל, והאימייל מאומתנוצר חשבון חדש, והוא מקושר לספק.
אין חשבון עם האימייל, והאימייל לא מאומתההתחברות נדחית עם 403 email_unverified, כדי ש-claim לא מאומת לא יוכל לתפוס כתובת אמיתית.
החשבון כבר מקושר לספק הזההמשתמש מתחבר.
החשבון מקושר לספק אחרההתחברות נדחית עם 403 account_linked_other_provider, גם כש-EXTERNAL_SSO_LINK_EXISTING מופעל.
קיים חשבון מקומי בלי SSO, EXTERNAL_SSO_LINK_EXISTING מופעל, והאימייל מאומתהחשבון מקושר לספק, והמשתמש מתחבר.
קיים חשבון מקומי בלי SSO, והדגל כבוי או שהאימייל לא מאומתההתחברות נדחית עם 403 account_exists.

המשתנה EXTERNAL_SSO_LINK_EXISTING כבוי כברירת מחדל, וזו ההגדרה שמגינה מפני השתלטות. רק הערכים true או 1, בלי הבדל בין אותיות גדולות לקטנות, מפעילים אותו. הפעילו אותו רק כשאתם בוטחים ב-claims של אימייל מאומת שמגיעים מספקי הזהות שלכם, עד כדי כך שאתם מוכנים לקשר אותם לחשבונות שכבר קיימים.

הדחיות account_exists ו-email_unverified מנוסחות באותו אופן לכל חשבון, כך שאי אפשר להשתמש בטופס ההתחברות כדי לגלות לאיזו כתובת יש תפקיד מיוחד. התגובה היא account_exists גם כשהכתובת תואמת יותר מחשבון אחד, כשהחיפוש נכשל וכששתי התחברויות מתחרות על יצירת אותו חשבון.

בפריסה עם חשבון חיוב

פריסה יכולה להגדיר חשבון חיוב אחד שמשלם על כל המשתמשים של ההתקנה; ראו את הדף ארנקים חיצוניים. הכללים לחשבון הזה מחמירים יותר, כי בו נמצאים הקרדיטים של הפריסה:

  • הוא מקושר בהתחברות המאומתת הראשונה שלו, בלי קשר לערך של EXTERNAL_SSO_LINK_EXISTING. assertion לא מאומת עבורו נדחה עם email_unverified, ו-assertion מספק אחר נדחה עם account_linked_other_provider.
  • כל התחברות מאוחרת יותר נבדקת שוב. האימייל חייב להיות מאומת, וה-subject חייב להיות זה שקישר את החשבון בפעם הראשונה. אחרת ההתחברות נדחית עם 403 account_linked_other_subject.
  • הוא שומר סיסמה כגישת חירום, ליום שבו ספק הזהות לא יכול להנפיק עבורו assertion. בפריסה שמאפשרת רק SSO, טופס הסיסמה נמצא ב-/login?billing=1, ורק חשבון החיוב יכול להתחבר דרכו.
  • מנהל של ההתקנה לא יכול לחסום או למחוק אותו (403 payer_account_protected), כך שתפקיד מנהל שספק הזהות של הפריסה עצמה מחלק לא יכול למנוע מההתקנה גישה לקרדיטים שלה.

אם ה-subject שספק הזהות נותן לחשבון החיוב משתנה אי פעם, ההתחברות של החשבון דרך SSO ממשיכה להיכשל עם account_linked_other_subject. החשבון עדיין יכול להתחבר עם הסיסמה שלו.

בפריסה עם חשבון חיוב, הכתובות של מנהלי הפלטפורמה (PLATFORM_OPERATOR_EMAILS, או PLATFORM_OWNER_EMAIL כשהמשתנה הראשון ריק) לעולם לא מקבלות קישור חדש. הן נדחות עם account_exists, וכך החשבונות של מנהלי הפלטפורמה נשארים מחוץ ל-SSO. חשבון של מנהל פלטפורמה שכבר מקושר ממשיך להתחבר.

ספקי OIDC ו-SAML

הסוגים oidc ו-saml מעבירים את ההתחברות ל-Supabase Auth של ההתקנה עצמה, שמאמת את התשובה של ספק הזהות. לשניהם יש מגבלות:

  • oidc: דף ההתחברות מעביר את ה-id של הספק כשם של ספק ה-OAuth. לכן ה-id חייב להיות בעצמו השם של ספק OAuth שמוגדר ב-Supabase Auth של ההתקנה, כגון keycloak או azure.
  • saml: דף ההתחברות מעביר את ה-id של הספק כדומיין ה-SAML. ה-id לא יכול להכיל נקודה, ולכן אי אפשר לבטא דומיין כגון acme.com, וספק מסוג saml לא עובד מקצה לקצה.
  • assertion שנשלח לספק מסוג oidc או saml נדחה עם 400 not_assertion_provider.

נקודות קצה

שתי נקודות הקצה ציבוריות ולא דורשות אסימון. אלה הנתיבים היחידים תחת /v1/sso/.

שיטהנתיבמה היא עושה
GET/v1/sso/providersמחזירה את רשימת הספקים לדף ההתחברות, עם id, label ו-kind בלבד, אף פעם לא סוד. מחזירה רשימה ריקה כש-SSO כבוי.
GET/v1/sso/:providerנקודת הקצה של ההחלפה. זו נקודת קצה של הפניות בדפדפן, לא JSON API.
סטטוסקודמתי
400no_assertionהכפתור נלחץ, ולספק אין initiateUrl.
400not_assertion_providerassertion נשלח לספק מסוג oidc או saml.
401invalid_signature, invalid_claims, expired, too_long_lived, missing_jti, missing_email, assertion_replayedה-assertion הפר כלל של החוזה.
403email_unverified, account_exists, account_linked_other_provider, account_linked_other_subjectכללי קישור החשבונות דחו את ההתחברות.
403sso_requiredפריסה שמאפשרת רק SSO דחתה סשן שלא הגיע דרך SSO.
404unknown_providerאין ספק עם ה-id הזה, או ש-SSO כבוי.
429rate_limit_exceededיותר מדי בקשות מכתובת IP אחת.

הערות אבטחה

  • השתמשו בסוד ייעודי. ה-secret הוא מפתח אימות של Nodaro בלבד, נפרד מסוד הסשנים של ספק הזהות עצמו.
  • כל assertion עובד פעם אחת. בדיקת ה-jti דוחה assertion שנשלח בפעם השנייה, ומגבלת אורך החיים שומרת על חלון זמן קצר.
  • ההפניות נשארות בתוך ההתקנה. Nodaro מקבל את next רק כנתיב יחסי באותה התקנה, שמתחיל ב-/ אבל לא ב-//. כל ערך אחר מוביל אל /projects.
  • שום סוד לא יוצא מהשרת. הנתיב GET /v1/sso/providers מחזיר רק id, label ו-kind. ה-assertion והאסימון החד־פעמי מושמטים מיומני הבקשות.
  • על ההחלפה חלה מגבלת קצב לכל כתובת IP.

שאלות נפוצות

האם SSO מופעל כברירת מחדל ב-Nodaro?

לא. עד ש-EXTERNAL_SSO_PROVIDERS מציין לפחות ספק אחד, דף ההתחברות לא מציג כפתור SSO, נקודת הקצה GET /v1/sso/providers מחזירה רשימה ריקה וכל נתיב של ספק מחזיר 404.

איך צריך לחתום על ה-assertion של SSO?

עם HS256 ועם סוד שאתם מגדירים לספק ולא משתמשים בו לשום דבר אחר. ה-assertion חייב לכלול גם את aud, את exp, מזהה jti ייחודי ואת האימייל, ו-Nodaro מקבל כל assertion פעם אחת בלבד.

האם assertion של SSO יכול להשתלט על חשבון קיים עם אותו אימייל?

לא כברירת מחדל. חשבון מקומי קיים מקושר רק כש-EXTERNAL_SSO_LINK_EXISTING הוא true וה-assertion מציין שהאימייל מאומת. חשבון שכבר מקושר לספק אחר לעולם לא מקושר שוב.

האם משתמש SSO הוא סוג מיוחד של חשבון Nodaro?

לא. אחרי שה-assertion מוחלף, יש למשתמש סשן רגיל. SSO הוא רק דרך להתחיל את הסשן הזה.

האם Nodaro תומך בספקי זהות של OIDC ושל SAML?

חלקית. הסוגים oidc ו-saml מעבירים את ההתחברות לשירות האימות של ההתקנה עצמה, עם מגבלות שמתוארות בדף הזה. החלפת ה-assertion היא הדרך שעובדת מקצה לקצה.

עודכן לאחרונה

בדף הזה