ניהול קובצי cookie של הפעלה

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

  • אבטחה משופרת באמצעות אסימוני סשן מבוססי JWT שאפשר ליצור רק באמצעות חשבונות שירות מורשים.
  • קובצי cookie ללא מצב של סשן, שמגיעים עם כל היתרונות של שימוש ב-JWT לאימות. קובץ ה-cookie של הסשן מכיל את אותן הצהרות (כולל הצהרות בהתאמה אישית) כמו אסימון המזהה, כך שאפשר לאכוף את אותן בדיקות הרשאות בקובצי ה-cookie של הסשן.
  • אפשרות ליצור קובצי cookie של סשן עם מועדי תפוגה מותאמים אישית שנעים בין 5 דקות לשבועיים.
  • גמישות לאכיפת מדיניות קובצי cookie על סמך דרישות האפליקציה: דומיין, נתיב, מאובטח, httpOnly וכו'.
  • יכולת לבטל קובצי cookie של סשן כשיש חשד לגניבת אסימון באמצעות ה-API הקיים לביטול אסימון רענון.
  • יכולת לזהות ביטול סשן במקרה של שינויים משמעותיים בחשבון.

כניסה

בהנחה שאפליקציה משתמשת בקובצי Cookie בצד השרת של httpOnly, נכנסים למשתמש בדף ההתחברות באמצעות ערכות ה-SDK של הלקוח. נוצר אסימון מזהה של Firebase, ולאחר מכן הוא נשלח באמצעות HTTP POST לנקודת קצה להתחברות לסשן, שבה נוצר קובץ cookie של סשן באמצעות Admin SDK. אם הפעולה תתבצע בהצלחה, המצב צריך להימחק מהאחסון בצד הלקוח.

firebase.initializeApp({
  apiKey: 'AIza…',
  authDomain: '<PROJECT_ID>.firebasepp.com'
});

// As httpOnly cookies are to be used, do not persist any state client side.
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);

// When the user signs in with email and password.
firebase.auth().signInWithEmailAndPassword('[email protected]', 'password').then(user => {
  // Get the user's ID token as it is needed to exchange for a session cookie.
  return user.getIdToken().then(idToken = > {
    // Session login endpoint is queried and the session cookie is set.
    // CSRF protection should be taken into account.
    // ...
    const csrfToken = getCookie('csrfToken')
    return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken);
  });
}).then(() => {
  // A page redirect would suffice as the persistence is set to NONE.
  return firebase.auth().signOut();
}).then(() => {
  window.location.assign('/profile');
});

כדי ליצור קובץ cookie של סשן בתמורה לאסימון המזהה שסופק, נדרש נקודת קצה מסוג HTTP. שולחים את האסימון לנקודת הקצה, ומגדירים משך סשן מותאם אישית באמצעות Firebase Admin SDK. צריך לנקוט אמצעים מתאימים כדי למנוע תקיפות של גניבת זהות משתמש – Cross-Site Request Forgery‏ (CSRF).

Node.js

app.post('/sessionLogin', (req, res) => {
  // Get the ID token passed and the CSRF token.
  const idToken = req.body.idToken.toString();
  const csrfToken = req.body.csrfToken.toString();
  // Guard against CSRF attacks.
  if (csrfToken !== req.cookies.csrfToken) {
    res.status(401).send('UNAUTHORIZED REQUEST!');
    return;
  }
  // Set session expiration to 5 days.
  const expiresIn = 60 * 60 * 24 * 5 * 1000;
  // Create the session cookie. This will also verify the ID token in the process.
  // The session cookie will have the same claims as the ID token.
  // To only allow session cookie setting on recent sign-in, auth_time in ID token
  // can be checked to ensure user was recently signed in before creating a session cookie.
  getAuth()
    .createSessionCookie(idToken, { expiresIn })
    .then(
      (sessionCookie) => {
        // Set cookie policy for session cookie.
        const options = { maxAge: expiresIn, httpOnly: true, secure: true };
        res.cookie('session', sessionCookie, options);
        res.end(JSON.stringify({ status: 'success' }));
      },
      (error) => {
        res.status(401).send('UNAUTHORIZED REQUEST!');
      }
    );
});

Java

@POST
@Path("/sessionLogin")
@Consumes("application/json")
public Response createSessionCookie(LoginRequest request) {
  // Get the ID token sent by the client
  String idToken = request.getIdToken();
  // Set session expiration to 5 days.
  long expiresIn = TimeUnit.DAYS.toMillis(5);
  SessionCookieOptions options = SessionCookieOptions.builder()
      .setExpiresIn(expiresIn)
      .build();
  try {
    // Create the session cookie. This will also verify the ID token in the process.
    // The session cookie will have the same claims as the ID token.
    String sessionCookie = FirebaseAuth.getInstance().createSessionCookie(idToken, options);
    // Set cookie policy parameters as required.
    NewCookie cookie = new NewCookie("session", sessionCookie /* ... other parameters */);
    return Response.ok().cookie(cookie).build();
  } catch (FirebaseAuthException e) {
    return Response.status(Status.UNAUTHORIZED).entity("Failed to create a session cookie")
        .build();
  }
}

Python

@app.route('/sessionLogin', methods=['POST'])
def session_login():
    # Get the ID token sent by the client
    id_token = flask.request.json['idToken']
    # Set session expiration to 5 days.
    expires_in = datetime.timedelta(days=5)
    try:
        # Create the session cookie. This will also verify the ID token in the process.
        # The session cookie will have the same claims as the ID token.
        session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
        response = flask.jsonify({'status': 'success'})
        # Set cookie policy for session cookie.
        expires = datetime.datetime.now() + expires_in
        response.set_cookie(
            'session', session_cookie, expires=expires, httponly=True, secure=True)
        return response
    except exceptions.FirebaseError:
        return flask.abort(401, 'Failed to create a session cookie')

Go

return func(w http.ResponseWriter, r *http.Request) {
	// Get the ID token sent by the client
	defer r.Body.Close()
	idToken, err := getIDTokenFromBody(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Set session expiration to 5 days.
	expiresIn := time.Hour * 24 * 5

	// Create the session cookie. This will also verify the ID token in the process.
	// The session cookie will have the same claims as the ID token.
	// To only allow session cookie setting on recent sign-in, auth_time in ID token
	// can be checked to ensure user was recently signed in before creating a session cookie.
	cookie, err := client.SessionCookie(r.Context(), idToken, expiresIn)
	if err != nil {
		http.Error(w, "Failed to create a session cookie", http.StatusInternalServerError)
		return
	}

	// Set cookie policy for session cookie.
	http.SetCookie(w, &http.Cookie{
		Name:     "session",
		Value:    cookie,
		MaxAge:   int(expiresIn.Seconds()),
		HttpOnly: true,
		Secure:   true,
	})
	w.Write([]byte(`{"status": "success"}`))
}

C#‎

// POST: /sessionLogin
[HttpPost]
public async Task<ActionResult> Login([FromBody] LoginRequest request)
{
    // Set session expiration to 5 days.
    var options = new SessionCookieOptions()
    {
        ExpiresIn = TimeSpan.FromDays(5),
    };

    try
    {
        // Create the session cookie. This will also verify the ID token in the process.
        // The session cookie will have the same claims as the ID token.
        var sessionCookie = await FirebaseAuth.DefaultInstance
            .CreateSessionCookieAsync(request.IdToken, options);

        // Set cookie policy parameters as required.
        var cookieOptions = new CookieOptions()
        {
            Expires = DateTimeOffset.UtcNow.Add(options.ExpiresIn),
            HttpOnly = true,
            Secure = true,
        };
        this.Response.Cookies.Append("session", sessionCookie, cookieOptions);
        return this.Ok();
    }
    catch (FirebaseAuthException)
    {
        return this.Unauthorized("Failed to create a session cookie");
    }
}

באפליקציות עם מידע רגיש, צריך לבדוק את auth_time לפני שמנפיקים את קובץ ה-cookie של הסשן, כדי לצמצם את חלון ההתקפה במקרה של גניבת אסימון מזהה:

Node.js

getAuth()
  .verifyIdToken(idToken)
  .then((decodedIdToken) => {
    // Only process if the user just signed in in the last 5 minutes.
    if (new Date().getTime() / 1000 - decodedIdToken.auth_time < 5 * 60) {
      // Create session cookie and set it.
      return getAuth().createSessionCookie(idToken, { expiresIn });
    }
    // A user that was not recently signed in is trying to set a session cookie.
    // To guard against ID token theft, require re-authentication.
    res.status(401).send('Recent sign in required!');
  });

Java

// To ensure that cookies are set only on recently signed in users, check auth_time in
// ID token before creating a cookie.
FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
long authTimeMillis = TimeUnit.SECONDS.toMillis(
    (long) decodedToken.getClaims().get("auth_time"));

// Only process if the user signed in within the last 5 minutes.
if (System.currentTimeMillis() - authTimeMillis < TimeUnit.MINUTES.toMillis(5)) {
  long expiresIn = TimeUnit.DAYS.toMillis(5);
  SessionCookieOptions options = SessionCookieOptions.builder()
      .setExpiresIn(expiresIn)
      .build();
  String sessionCookie = FirebaseAuth.getInstance().createSessionCookie(idToken, options);
  // Set cookie policy parameters as required.
  NewCookie cookie = new NewCookie("session", sessionCookie);
  return Response.ok().cookie(cookie).build();
}
// User did not sign in recently. To guard against ID token theft, require
// re-authentication.
return Response.status(Status.UNAUTHORIZED).entity("Recent sign in required").build();

Python

# To ensure that cookies are set only on recently signed in users, check auth_time in
# ID token before creating a cookie.
try:
    decoded_claims = auth.verify_id_token(id_token)
    # Only process if the user signed in within the last 5 minutes.
    if time.time() - decoded_claims['auth_time'] < 5 * 60:
        expires_in = datetime.timedelta(days=5)
        expires = datetime.datetime.now() + expires_in
        session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
        response = flask.jsonify({'status': 'success'})
        response.set_cookie(
            'session', session_cookie, expires=expires, httponly=True, secure=True)
        return response
    # User did not sign in recently. To guard against ID token theft, require
    # re-authentication.
    return flask.abort(401, 'Recent sign in required')
except auth.InvalidIdTokenError:
    return flask.abort(401, 'Invalid ID token')
except exceptions.FirebaseError:
    return flask.abort(401, 'Failed to create a session cookie')

Go

return func(w http.ResponseWriter, r *http.Request) {
	// Get the ID token sent by the client
	defer r.Body.Close()
	idToken, err := getIDTokenFromBody(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	decoded, err := client.VerifyIDToken(r.Context(), idToken)
	if err != nil {
		http.Error(w, "Invalid ID token", http.StatusUnauthorized)
		return
	}
	// Return error if the sign-in is older than 5 minutes.
	if time.Now().Unix()-decoded.Claims["auth_time"].(int64) > 5*60 {
		http.Error(w, "Recent sign-in required", http.StatusUnauthorized)
		return
	}

	expiresIn := time.Hour * 24 * 5
	cookie, err := client.SessionCookie(r.Context(), idToken, expiresIn)
	if err != nil {
		http.Error(w, "Failed to create a session cookie", http.StatusInternalServerError)
		return
	}
	http.SetCookie(w, &http.Cookie{
		Name:     "session",
		Value:    cookie,
		MaxAge:   int(expiresIn.Seconds()),
		HttpOnly: true,
		Secure:   true,
	})
	w.Write([]byte(`{"status": "success"}`))
}

C#‎

// To ensure that cookies are set only on recently signed in users, check auth_time in
// ID token before creating a cookie.
var decodedToken = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
var authTime = new DateTime(1970, 1, 1).AddSeconds(
    (long)decodedToken.Claims["auth_time"]);

// Only process if the user signed in within the last 5 minutes.
if (DateTime.UtcNow - authTime < TimeSpan.FromMinutes(5))
{
    var options = new SessionCookieOptions()
    {
        ExpiresIn = TimeSpan.FromDays(5),
    };
    var sessionCookie = await FirebaseAuth.DefaultInstance.CreateSessionCookieAsync(
        idToken, options);
    // Set cookie policy parameters as required.
    this.Response.Cookies.Append("session", sessionCookie);
    return this.Ok();
}

// User did not sign in recently. To guard against ID token theft, require
// re-authentication.
return this.Unauthorized("Recent sign in required");

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

Node.js

// Whenever a user is accessing restricted content that requires authentication.
app.post('/profile', (req, res) => {
  const sessionCookie = req.cookies.session || '';
  // Verify the session cookie. In this case an additional check is added to detect
  // if the user's Firebase session was revoked, user deleted/disabled, etc.
  getAuth()
    .verifySessionCookie(sessionCookie, true /** checkRevoked */)
    .then((decodedClaims) => {
      serveContentForUser('/profile', req, res, decodedClaims);
    })
    .catch((error) => {
      // Session cookie is unavailable or invalid. Force user to login.
      res.redirect('/login');
    });
});

Java

@POST
@Path("/profile")
public Response verifySessionCookie(@CookieParam("session") Cookie cookie) {
  String sessionCookie = cookie.getValue();
  try {
    // Verify the session cookie. In this case an additional check is added to detect
    // if the user's Firebase session was revoked, user deleted/disabled, etc.
    final boolean checkRevoked = true;
    FirebaseToken decodedToken = FirebaseAuth.getInstance().verifySessionCookie(
        sessionCookie, checkRevoked);
    return serveContentForUser(decodedToken);
  } catch (FirebaseAuthException e) {
    // Session cookie is unavailable, invalid or revoked. Force user to login.
    return Response.temporaryRedirect(URI.create("/login")).build();
  }
}

Python

@app.route('/profile', methods=['POST'])
def access_restricted_content():
    session_cookie = flask.request.cookies.get('session')
    if not session_cookie:
        # Session cookie is unavailable. Force user to login.
        return flask.redirect('/login')

    # Verify the session cookie. In this case an additional check is added to detect
    # if the user's Firebase session was revoked, user deleted/disabled, etc.
    try:
        decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
        return serve_content_for_user(decoded_claims)
    except auth.InvalidSessionCookieError:
        # Session cookie is invalid, expired or revoked. Force user to login.
        return flask.redirect('/login')

Go

return func(w http.ResponseWriter, r *http.Request) {
	// Get the ID token sent by the client
	cookie, err := r.Cookie("session")
	if err != nil {
		// Session cookie is unavailable. Force user to login.
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}

	// Verify the session cookie. In this case an additional check is added to detect
	// if the user's Firebase session was revoked, user deleted/disabled, etc.
	decoded, err := client.VerifySessionCookieAndCheckRevoked(r.Context(), cookie.Value)
	if err != nil {
		// Session cookie is invalid. Force user to login.
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}

	serveContentForUser(w, r, decoded)
}

C#‎

// POST: /profile
[HttpPost]
public async Task<ActionResult> Profile()
{
    var sessionCookie = this.Request.Cookies["session"];
    if (string.IsNullOrEmpty(sessionCookie))
    {
        // Session cookie is not available. Force user to login.
        return this.Redirect("/login");
    }

    try
    {
        // Verify the session cookie. In this case an additional check is added to detect
        // if the user's Firebase session was revoked, user deleted/disabled, etc.
        var checkRevoked = true;
        var decodedToken = await FirebaseAuth.DefaultInstance.VerifySessionCookieAsync(
            sessionCookie, checkRevoked);
        return ViewContentForUser(decodedToken);
    }
    catch (FirebaseAuthException)
    {
        // Session cookie is invalid or revoked. Force user to login.
        return this.Redirect("/login");
    }
}

מאמתים את קובצי ה-cookie של הסשן באמצעות ה-API verifySessionCookie ב-Admin SDK. מדובר בפעולה לתקורה נמוכה. האישורים הציבוריים נשלחות בתחילה לשאילתה ומאוחסנים במטמון עד שתוקפם יפוג. אפשר לבצע אימות של קובצי cookie של סשנים באמצעות האישורים הציבוריים שנשמרו במטמון, בלי בקשות נוספות לרשת.

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

מטעמי אבטחה, אי אפשר להשתמש בקובצי cookie לסשן של Firebase בשירותי Firebase אחרים בגלל תקופת התוקף המותאמת אישית שלהם, שאפשר להגדיר למשך שבועיים לכל היותר. כל האפליקציות שמשתמשות בקובצי cookie בצד השרת צפויות לאכוף בדיקות הרשאות אחרי אימות קובצי ה-cookie האלה בצד השרת.

Node.js

getAuth()
  .verifySessionCookie(sessionCookie, true)
  .then((decodedClaims) => {
    // Check custom claims to confirm user is an admin.
    if (decodedClaims.admin === true) {
      return serveContentForAdmin('/admin', req, res, decodedClaims);
    }
    res.status(401).send('UNAUTHORIZED REQUEST!');
  })
  .catch((error) => {
    // Session cookie is unavailable or invalid. Force user to login.
    res.redirect('/login');
  });

Java

try {
  final boolean checkRevoked = true;
  FirebaseToken decodedToken = FirebaseAuth.getInstance().verifySessionCookie(
      sessionCookie, checkRevoked);
  if (Boolean.TRUE.equals(decodedToken.getClaims().get("admin"))) {
    return serveContentForAdmin(decodedToken);
  }
  return Response.status(Status.UNAUTHORIZED).entity("Insufficient permissions").build();
} catch (FirebaseAuthException e) {
  // Session cookie is unavailable, invalid or revoked. Force user to login.
  return Response.temporaryRedirect(URI.create("/login")).build();
}

Python

try:
    decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
    # Check custom claims to confirm user is an admin.
    if decoded_claims.get('admin') is True:
        return serve_content_for_admin(decoded_claims)

    return flask.abort(401, 'Insufficient permissions')
except auth.InvalidSessionCookieError:
    # Session cookie is invalid, expired or revoked. Force user to login.
    return flask.redirect('/login')

Go

return func(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("session")
	if err != nil {
		// Session cookie is unavailable. Force user to login.
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}

	decoded, err := client.VerifySessionCookieAndCheckRevoked(r.Context(), cookie.Value)
	if err != nil {
		// Session cookie is invalid. Force user to login.
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}

	// Check custom claims to confirm user is an admin.
	if decoded.Claims["admin"] != true {
		http.Error(w, "Insufficient permissions", http.StatusUnauthorized)
		return
	}

	serveContentForAdmin(w, r, decoded)
}

C#‎

try
{
    var checkRevoked = true;
    var decodedToken = await FirebaseAuth.DefaultInstance.VerifySessionCookieAsync(
        sessionCookie, checkRevoked);
    object isAdmin;
    if (decodedToken.Claims.TryGetValue("admin", out isAdmin) && (bool)isAdmin)
    {
        return ViewContentForAdmin(decodedToken);
    }

    return this.Unauthorized("Insufficient permissions");
}
catch (FirebaseAuthException)
{
    // Session cookie is invalid or revoked. Force user to login.
    return this.Redirect("/login");
}

יציאה מהחשבון

כשמשתמש יוצא מהחשבון בצד הלקוח, צריך לטפל בכך בצד השרת באמצעות נקודת קצה. בקשת POST/GET אמורה לנקות את קובץ ה-cookie של הסשן. חשוב לזכור שלמרות שקובץ ה-cookie נמחק, הוא נשאר פעיל עד שפג התוקף הטבעי שלו.

Node.js

app.post('/sessionLogout', (req, res) => {
  res.clearCookie('session');
  res.redirect('/login');
});

Java

@POST
@Path("/sessionLogout")
public Response clearSessionCookie(@CookieParam("session") Cookie cookie) {
  final int maxAge = 0;
  NewCookie newCookie = new NewCookie(cookie, null, maxAge, true);
  return Response.temporaryRedirect(URI.create("/login")).cookie(newCookie).build();
}

Python

@app.route('/sessionLogout', methods=['POST'])
def session_logout():
    response = flask.make_response(flask.redirect('/login'))
    response.set_cookie('session', expires=0)
    return response

Go

return func(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:   "session",
		Value:  "",
		MaxAge: 0,
	})
	http.Redirect(w, r, "/login", http.StatusFound)
}

C#‎

// POST: /sessionLogout
[HttpPost]
public ActionResult ClearSessionCookie()
{
    this.Response.Cookies.Delete("session");
    return this.Redirect("/login");
}

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

Node.js

app.post('/sessionLogout', (req, res) => {
  const sessionCookie = req.cookies.session || '';
  res.clearCookie('session');
  getAuth()
    .verifySessionCookie(sessionCookie)
    .then((decodedClaims) => {
      return getAuth().revokeRefreshTokens(decodedClaims.sub);
    })
    .then(() => {
      res.redirect('/login');
    })
    .catch((error) => {
      res.redirect('/login');
    });
});

Java

@POST
@Path("/sessionLogout")
public Response clearSessionCookieAndRevoke(@CookieParam("session") Cookie cookie) {
  String sessionCookie = cookie.getValue();
  try {
    FirebaseToken decodedToken = FirebaseAuth.getInstance().verifySessionCookie(sessionCookie);
    FirebaseAuth.getInstance().revokeRefreshTokens(decodedToken.getUid());
    final int maxAge = 0;
    NewCookie newCookie = new NewCookie(cookie, null, maxAge, true);
    return Response.temporaryRedirect(URI.create("/login")).cookie(newCookie).build();
  } catch (FirebaseAuthException e) {
    return Response.temporaryRedirect(URI.create("/login")).build();
  }
}

Python

@app.route('/sessionLogout', methods=['POST'])
def session_logout():
    session_cookie = flask.request.cookies.get('session')
    try:
        decoded_claims = auth.verify_session_cookie(session_cookie)
        auth.revoke_refresh_tokens(decoded_claims['sub'])
        response = flask.make_response(flask.redirect('/login'))
        response.set_cookie('session', expires=0)
        return response
    except auth.InvalidSessionCookieError:
        return flask.redirect('/login')

Go

return func(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("session")
	if err != nil {
		// Session cookie is unavailable. Force user to login.
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}

	decoded, err := client.VerifySessionCookie(r.Context(), cookie.Value)
	if err != nil {
		// Session cookie is invalid. Force user to login.
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}
	if err := client.RevokeRefreshTokens(r.Context(), decoded.UID); err != nil {
		http.Error(w, "Failed to revoke refresh token", http.StatusInternalServerError)
		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:   "session",
		Value:  "",
		MaxAge: 0,
	})
	http.Redirect(w, r, "/login", http.StatusFound)
}

C#‎

// POST: /sessionLogout
[HttpPost]
public async Task<ActionResult> ClearSessionCookieAndRevoke()
{
    var sessionCookie = this.Request.Cookies["session"];
    try
    {
        var decodedToken = await FirebaseAuth.DefaultInstance
            .VerifySessionCookieAsync(sessionCookie);
        await FirebaseAuth.DefaultInstance.RevokeRefreshTokensAsync(decodedToken.Uid);
        this.Response.Cookies.Delete("session");
        return this.Redirect("/login");
    }
    catch (FirebaseAuthException)
    {
        return this.Redirect("/login");
    }
}

אימות קובצי ה-Cookie של הסשן באמצעות ספריית JWT של צד שלישי

אם הקצה העורפי שלכם נכתב בשפה שלא נתמכת על ידי Firebase Admin SDK, עדיין תוכלו לאמת קובצי cookie של סשן. קודם כול, מוצאים ספריית JWT של צד שלישי בשביל השפה שלכם. לאחר מכן, מאמתים את הכותרת, את עומס העבודה ואת החתימה של קובץ ה-cookie של הסשן.

מוודאים שהכותרת של קובץ ה-cookie של הסשן עומדת באילוצים הבאים:

הצהרות בכותרת של קובץ cookie זמני ב-Firebase
alg אלגוריתם "RS256"
kid מזהה המפתח חייב להתאים לאחד מהמפתחות הציבוריים שמפורטים ב-https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys

מוודאים שעומס העבודה של קובץ ה-cookie של הסשן עומד באילוצים הבאים:

הצהרות על עומסי נתונים של קובצי cookie של סשן ב-Firebase
exp מועד תפוגה התאריך חייב להיות בעתיד. הזמן נמדד בשניות מאז ראשית זמן יוניקס (Unix epoch). התפוגה מוגדרת לפי משך הזמן המותאם אישית שצוין כשקובץ ה-cookie נוצר.
iat שעת ההנפקה התאריך חייב להיות בעבר. הזמן נמדד בשניות מאז ראשית זמן יוניקס (Unix epoch).
aud קהל צריך להזין את מזהה הפרויקט ב-Firebase, המזהה הייחודי של הפרויקט ב-Firebase. אפשר למצוא אותו בכתובת ה-URL של מסוף הפרויקט.
iss המנפיק חייב להיות "https://session.firebase.google.com/<projectId>"", כאשר <projectId> הוא אותו מזהה פרויקט ששימש ב-aud שלמעלה.
sub נושא המחרוזת חייבת להיות לא ריקה, והיא חייבת להיות הערך של uid של המשתמש או המכשיר.
auth_time שעת האימות התאריך חייב להיות בעבר. השעה שבה המשתמש ביצע אימות. הערך הזה תואם ל-auth_time של אסימון המזהה ששימש ליצירת קובץ ה-cookie של הסשן.

לבסוף, מוודאים שקובץ ה-cookie של הסשן נחתם על ידי המפתח הפרטי שתואם להצהרת הצאצא של האסימון. מקבלים את המפתח הציבורי מ-https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys ומשתמשים בספריית JWT כדי לאמת את החתימה. כדי לקבוע מתי לרענן את המפתחות הציבוריים, משתמשים בערך של max-age בכותרת Cache-Control בתשובה מנקודת הקצה הזו.

אם כל הבדיקות שלמעלה עוברות בהצלחה, אפשר להשתמש בנושא (sub) של קובץ ה-cookie של הסשן בתור מזהה המשתמש או המכשיר התואם.