จัดการคุกกี้ของเซสชัน

Firebase Auth ให้บริการการจัดการคุกกี้เซสชันฝั่งเซิร์ฟเวอร์สำหรับเว็บไซต์แบบดั้งเดิมที่อาศัยคุกกี้เซสชัน โซลูชันนี้มีข้อดีมากกว่าโทเค็นรหัสที่มีอายุสั้นของฝั่งไคลเอ็นต์ กล่าวคืออาจมีกลไกการเปลี่ยนเส้นทางทุกครั้งเพื่ออัปเดตคุกกี้ของเซสชันเมื่อหมดอายุ

  • การรักษาความปลอดภัยที่เพิ่มขึ้นผ่านโทเค็นเซสชันแบบ JWT ที่สร้างได้โดยใช้บัญชีบริการที่ได้รับอนุญาตเท่านั้น
  • คุกกี้เซสชันแบบไม่เก็บสถานะที่มาพร้อมกับประโยชน์ทั้งหมดของการใช้ JWT สำหรับการตรวจสอบสิทธิ์ คุกกี้เซสชันมีการอ้างสิทธิ์ (รวมถึงการอ้างสิทธิ์ที่กำหนดเอง) เหมือนกับโทเค็นรหัส ทำให้มีการตรวจสอบสิทธิ์แบบเดียวกันที่สามารถบังคับใช้กับคุกกี้ของเซสชันได้
  • สามารถสร้างคุกกี้ของเซสชันที่มีเวลาหมดอายุที่กำหนดเองตั้งแต่ 5 นาทีถึง 2 สัปดาห์
  • ความยืดหยุ่นในการบังคับใช้นโยบายคุกกี้ตามข้อกำหนดของแอปพลิเคชัน เช่น โดเมน, เส้นทาง, ความปลอดภัย, httpOnly ฯลฯ
  • ความสามารถในการเพิกถอนคุกกี้ของเซสชันเมื่อสงสัยว่ามีการโจรกรรมโทเค็นโดยใช้ API การเพิกถอนโทเค็นการรีเฟรชที่มีอยู่
  • ความสามารถในการตรวจจับการเพิกถอนเซสชันจากการเปลี่ยนแปลงที่สำคัญของบัญชี

ลงชื่อเข้าใช้

สมมติว่าแอปพลิเคชันใช้คุกกี้ฝั่งเซิร์ฟเวอร์ httpOnly ให้ลงชื่อเข้าใช้ผู้ใช้ในหน้าการเข้าสู่ระบบโดยใช้ SDK ของไคลเอ็นต์ ระบบจะสร้างโทเค็นรหัส Firebase แล้วส่งโทเค็นรหัสผ่านทาง HTTP POST ไปยังปลายทางการเข้าสู่ระบบเซสชันที่สร้างคุกกี้ของเซสชันโดยใช้ 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('user@example.com', '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');
});

หากต้องการสร้างคุกกี้เซสชันเพื่อแลกเปลี่ยนกับโทเค็นรหัสที่ให้ไว้ คุณต้องระบุปลายทาง HTTP ส่งโทเค็นไปยังปลายทางโดยตั้งค่าระยะเวลาเซสชันที่กำหนดเองโดยใช้ Firebase Admin SDK ควรใช้มาตรการที่เหมาะสมเพื่อป้องกันการโจมตีโดยการปลอมแปลงคำขอแบบข้ามเว็บไซต์ (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 ก่อนออกคุกกี้เซสชัน เพื่อลดกรอบเวลาการโจมตีในกรณีที่โทเค็น ID ถูกขโมย

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");

หลังจากลงชื่อเข้าใช้ ส่วนที่ป้องกันการเข้าถึงทั้งหมดในเว็บไซต์ควรตรวจสอบคุกกี้เซสชันและยืนยันก่อนแสดงเนื้อหาที่ถูกจำกัดตามกฎความปลอดภัยบางอย่าง

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");
    }
}

ยืนยันคุกกี้เซสชันโดยใช้ Admin SDK verifySessionCookie API ซึ่งเป็นการทำงานแบบโอเวอร์เฮดต่ำ ระบบจะค้นหาและแคชใบรับรองสาธารณะในขั้นต้นจนกว่าจะหมดอายุ การยืนยันคุกกี้เซสชันทําได้ด้วยใบรับรองสาธารณะที่แคชไว้โดยไม่ต้องมีคำขอเครือข่ายเพิ่มเติม

หากคุกกี้ไม่ถูกต้อง ให้ล้างข้อมูลและขอให้ผู้ใช้ลงชื่อเข้าใช้อีกครั้ง มีตัวเลือกเพิ่มเติมในการตรวจสอบการเพิกถอนเซสชัน โปรดทราบว่าการดำเนินการนี้จะเพิ่มคำขอเครือข่ายเพิ่มเติมทุกครั้งที่มีการยืนยันคุกกี้เซสชัน

ด้วยเหตุผลด้านความปลอดภัย คุกกี้เซสชันของ Firebase จะใช้กับบริการ Firebase อื่นๆ ไม่ได้ เนื่องจากมีระยะเวลาความถูกต้องที่กำหนดเอง ซึ่งตั้งค่าได้สูงสุด 2 สัปดาห์ แอปพลิเคชันทั้งหมดที่ใช้คุกกี้ฝั่งเซิร์ฟเวอร์ควรบังคับใช้การตรวจสอบสิทธิ์หลังจากยืนยันคุกกี้ฝั่งเซิร์ฟเวอร์เหล่านี้

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 ควรส่งผลให้มีการล้างคุกกี้เซสชัน โปรดทราบว่าแม้จะล้างคุกกี้แล้ว แต่คุกกี้จะยังคงใช้งานได้จนกว่าจะหมดอายุตามปกติ

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");
    }
}

ยืนยันคุกกี้เซสชันโดยใช้ไลบรารี JWT ของบุคคลที่สาม

หากแบ็กเอนด์เป็นภาษาที่ Firebase Admin SDK ไม่รองรับ คุณจะยังยืนยันคุกกี้เซสชันได้ ก่อนอื่น ให้หาไลบรารี JWT ของบุคคลที่สามสำหรับภาษาของคุณ จากนั้นตรวจสอบส่วนหัว เพย์โหลด และลายเซ็นของคุกกี้เซสชัน

ตรวจสอบว่าส่วนหัวของคุกกี้เซสชันสอดคล้องกับข้อจำกัดต่อไปนี้

การอ้างสิทธิ์ส่วนหัวคุกกี้เซสชันของ Firebase
alg อัลกอริทึม "RS256"
kid รหัสคีย์ ต้องตรงกับคีย์สาธารณะที่ระบุไว้ใน https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys

ตรวจสอบว่าเพย์โหลดของคุกกี้เซสชันเป็นไปตามข้อจำกัดต่อไปนี้

การอ้างสิทธิ์เพย์โหลดคุกกี้เซสชันของ Firebase
exp เวลาหมดอายุ ต้องเป็นวันที่ในอนาคต เวลาจะวัดในหน่วยวินาทีนับตั้งแต่ UNIX Epoch วันที่หมดอายุจะตั้งตามระยะเวลาที่กำหนดเองซึ่งระบุไว้เมื่อสร้างคุกกี้
iat เวลาที่ออก ต้องเป็นวันที่ในอดีต เวลาจะวัดในหน่วยวินาทีนับตั้งแต่ UNIX Epoch
aud กลุ่มเป้าหมาย ต้องเป็นรหัสโปรเจ็กต์ Firebase ซึ่งเป็นตัวระบุที่ไม่ซ้ำกันสำหรับโปรเจ็กต์ Firebase ของคุณ ซึ่งดูได้ใน URL ของคอนโซลโปรเจ็กต์นั้น
iss ผู้ออก ต้องเป็น "https://session.firebase.google.com/<projectId>"" โดยที่ <projectId> คือรหัสโปรเจ็กต์เดียวกันกับที่ใช้สำหรับ aud ด้านบน
sub เรื่อง ต้องเป็นสตริงที่ไม่ว่างเปล่าและต้องเป็น uid ของผู้ใช้หรืออุปกรณ์
auth_time เวลาในการตรวจสอบสิทธิ์ ต้องเป็นวันที่ในอดีต เวลาที่ผู้ใช้ตรวจสอบสิทธิ์ ค่านี้ตรงกับ auth_time ของโทเค็นรหัสที่ใช้สร้างคุกกี้เซสชัน

สุดท้าย ให้ตรวจสอบว่าคีย์ส่วนตัวเซสชันมีการลงชื่อคุกกี้เซสชันที่ตรงกับการอ้างสิทธิ์เด็กของโทเค็น รับคีย์สาธารณะจาก https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys และใช้ไลบรารี JWT เพื่อยืนยันลายเซ็น ใช้ค่าของ max-age ในส่วนหัว Cache-Control ของการตอบสนองจากปลายทางนั้นเพื่อกำหนดว่าควรรีเฟรชคีย์สาธารณะเมื่อใด

หากการยืนยันทั้งหมดข้างต้นประสบความสำเร็จ คุณสามารถใช้หัวเรื่อง (sub) ของคุกกี้เซสชันเป็น UID ของผู้ใช้หรืออุปกรณ์ที่เกี่ยวข้อง