توفّر خدمة Firebase Auth إدارة ملفات تعريف ارتباط الجلسة من جهة الخادم للمواقع الإلكترونية التقليدية التي تعتمد على ملفات تعريف ارتباط الجلسة. يتميّز هذا الحلّ بعدّة مزايا مقارنةً برموز التعريف المؤقتة من جهة العميل، والتي قد تتطلّب آلية إعادة توجيه في كل مرة لتعديل ملف تعريف ارتباط الجلسة عند انتهاء صلاحيته:
- تحسين الأمان من خلال رموز الجلسات المستندة إلى JWT والتي لا يمكن إنشاؤها إلا باستخدام حسابات الخدمة المصرّح بها
- ملفات تعريف ارتباط الجلسة غير الاحتفاظ بالحالة التي تتضمّن جميع مزايا استخدام رموز JWT للمصادقة يحتوي ملف تعريف ارتباط الجلسة على المطالبات نفسها (بما في ذلك المطالبات المخصّصة) مثل رمز التعريف، ما يجعل عمليات التحقّق من الأذونات نفسها قابلة للتنفيذ على ملفات تعريف ارتباط الجلسة.
- إمكانية إنشاء ملفات تعريف ارتباط خاصة بالجلسة مع أوقات انتهاء صلاحية مخصّصة تتراوح بين 5 دقائق وأسبوعَين
- المرونة في فرض سياسات ملفات تعريف الارتباط استنادًا إلى متطلبات التطبيق: النطاق والمسار والأمان و
httpOnly
وما إلى ذلك - إمكانية إبطال ملفات تعريف الارتباط الخاصة بالجلسة عند الاشتباه في سرقة الرمز المميّز باستخدام واجهة برمجة التطبيقات الحالية لإبطال الرمز المميّز لإعادة التحميل
- إمكانية رصد إبطال الجلسة عند إجراء تغييرات كبيرة في الحساب
تسجيل الدخول
بافتراض أنّ أحد التطبيقات يستخدم ملفات تعريف الارتباط من جهة الخادم 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. أرسِل الرمز المميّز إلى نقطة النهاية، مع ضبط مدة الجلسة المخصّصة باستخدام حزمة تطوير البرامج (SDK) للمشرف في Firebase. يجب اتّخاذ التدابير المناسبة لمنع هجمات تقليد الطلبات من موقع إلكتروني مختلف (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");
}
}
تحقَّق من ملفات تعريف ارتباط الجلسة باستخدام واجهة برمجة التطبيقات verifySessionCookie في Admin SDK. هذه عملية ذات تكلفة منخفضة. يتم في البداية طلب الشهادات العامة وتخزينها مؤقتًا إلى أن تنتهي صلاحيتها. يمكن إثبات صحة ملفات تعريف الارتباط الخاصة بالجلسة باستخدام الشهادات العامة المخزّنة مؤقتًا بدون أي طلبات إضافية على الشبكة.
إذا كان ملف تعريف الارتباط غير صالح، تأكَّد من محوه، واطلب من المستخدم تسجيل الدخول مرة أخرى. يتوفّر خيار إضافي للتحقّق من إبطال الجلسة. يُرجى العِلم أنّ ذلك يضيف طلب شبكة إضافيًا في كل مرة يتم فيها التحقّق من ملف تعريف ارتباط الجلسة.
لأسباب تتعلّق بالأمان، لا يمكن استخدام ملفات تعريف الارتباط الخاصة بالجلسات في Firebase مع خدمات Firebase الأخرى بسبب فترة صلاحيتها المخصّصة التي يمكن ضبطها على مدة أقصاها أسبوعان. من المتوقّع أن تفرض جميع التطبيقات التي تستخدم ملفات تعريف الارتباط من جهة الخادم عمليات التحقّق من الأذونات بعد التحقّق من صحة ملفات تعريف الارتباط هذه من جهة الخادم.
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");
}
يؤدي استدعاء واجهة برمجة التطبيقات الخاصة بالإبطال إلى إبطال الجلسة وإبطال جميع جلسات المستخدم الأخرى، ما يفرض تسجيل الدخول من جديد. بالنسبة إلى التطبيقات الحسّاسة، يُنصح بتقصير مدة الجلسة.
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 تابعة لجهة خارجية
إذا كانت الخلفية بلغة غير متوافقة مع حزمة تطوير البرامج (SDK) الخاصة بـ Firebase Admin، سيظل بإمكانك إثبات صحة ملفات تعريف الارتباط الخاصة بالجلسة. أولاً، ابحث عن مكتبة JWT تابعة لجهة خارجية بلغتك. بعد ذلك، تحقَّق من العنوان والحمولة والتوقيع لملف تعريف الارتباط الخاص بالجلسة.
تأكَّد من أنّ عنوان ملف تعريف ارتباط الجلسة يتوافق مع القيود التالية:
مطالبات عنوان ملف تعريف الارتباط للجلسة في Firebase | ||
---|---|---|
alg |
خوارزمية | "RS256" |
kid |
معرّف المفتاح |
يجب أن يتوافق مع أحد المفاتيح العامة المدرَجة في
https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys
|
تأكَّد من أنّ حمولة ملف تعريف ارتباط الجلسة تتوافق مع القيود التالية:
مطالبات حمولة ملف تعريف الارتباط للجلسة في Firebase | ||
---|---|---|
exp |
وقت انتهاء الصلاحية | يجب أن يكون في المستقبل. ويتم قياس الوقت بالثواني منذ بدء حساب الفترة في نظام التشغيل UNIX. يتم ضبط تاريخ انتهاء الصلاحية استنادًا إلى المدة المخصّصة التي يتم تقديمها عند إنشاء ملف تعريف الارتباط. |
iat |
وقت الإصدار | يجب أن يكون التاريخ في الماضي. ويتم قياس الوقت بالثواني منذ بدء حساب الفترة في نظام التشغيل 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
) ملف تعريف ارتباط الجلسة كمعرّف فريد للمستخدم أو الجهاز المعنيّ.