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 ของผู้ใช้หรืออุปกรณ์ที่เกี่ยวข้อง