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
ก่อนออกคุกกี้เซสชัน เพื่อลดกรอบเวลาการโจมตีในกรณีที่มีการขโมยโทเค็นระบุตัวตน
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 |
เวลาหมดอายุ | ต้องเป็นวันที่ในอนาคต เวลาที่วัดเป็นวินาทีนับตั้งแต่ Epoch ของ UNIX ระบบจะตั้งค่าวันหมดอายุตามระยะเวลาที่กำหนดเองเมื่อสร้างคุกกี้ |
iat |
เวลาที่ออก | ต้องเป็นวันที่ที่ผ่านมาแล้ว เวลาที่วัดเป็นวินาทีนับตั้งแต่ Epoch ของ UNIX |
aud |
กลุ่มเป้าหมาย | ต้องเป็นรหัสโปรเจ็กต์ Firebase ซึ่งเป็นตัวระบุที่ไม่ซ้ำสำหรับโปรเจ็กต์ Firebase ของคุณ ซึ่งดูได้ที่ URL ของคอนโซลโปรเจ็กต์นั้น |
iss |
ผู้ออก |
ต้องเท่ากับ "https://session.firebase.google.com/<projectId>" "
โดยที่ <projectId> คือรหัสโปรเจ็กต์เดียวกับที่ใช้สำหรับ aud ด้านบน
|
sub |
เรื่อง |
ต้องไม่ใช่สตริงว่างและต้องเป็น uid ของผู้ใช้หรืออุปกรณ์
|
auth_time
|
เวลาการตรวจสอบสิทธิ์ | ต้องเป็นวันที่ที่ผ่านมาแล้ว เวลาเมื่อผู้ใช้ตรวจสอบสิทธิ์ ซึ่งจะตรงกับ auth_time ของโทเค็นระบุตัวตนที่ใช้สร้างคุกกี้เซสชัน
|
สุดท้าย ให้ตรวจสอบว่าคุกกี้เซสชันได้รับการลงนามโดยคีย์ส่วนตัวที่สอดคล้องกับการอ้างสิทธิ์ "kid" ของโทเค็น รับคีย์สาธารณะจาก https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys
และใช้ไลบรารี JWT เพื่อตรวจสอบลายเซ็น ใช้ค่า max-age ในส่วนหัว Cache-Control
ของคำตอบจากปลายทางนั้นเพื่อกำหนดเวลาในการรีเฟรชคีย์สาธารณะ
หากการยืนยันทั้งหมดข้างต้นสำเร็จ คุณสามารถใช้เรื่อง (sub
) ของคุกกี้เซสชันเป็น uid ของผู้ใช้หรืออุปกรณ์ที่เกี่ยวข้อง