تمنحك Firebase تحكّمًا كاملاً في المصادقة من خلال السماح لك بمصادقة المستخدمين أو الأجهزة باستخدام رموز JSON المميّزة (JWT) الآمنة. يمكنك إنشاء هذه الرموز المميّزة على خادمك، وإرسالها مرة أخرى إلى جهاز العميل، ثم استخدامها للمصادقة من خلال طريقة signInWithCustomToken().
لتحقيق ذلك، عليك إنشاء نقطة نهاية للخادم تقبل بيانات تسجيل الدخول، مثل اسم المستخدم وكلمة المرور، وإذا كانت بيانات الاعتماد صالحة، تعرض رمز JWT مخصّصًا. يمكن لجهاز العميل بعد ذلك استخدام رمز JWT المخصّص الذي يعرضه خادمك للمصادقة باستخدام Firebase
(iOS+ وAndroid والويب). بعد المصادقة، سيتم استخدام هذه الهوية عند الوصول إلى خدمات Firebase الأخرى، مثل Firebase Realtime Database
و Cloud Storage. علاوةً على ذلك، ستتوفّر محتويات رمز JWT في
العنصر auth في
Realtime Database Security Rules والعنصر
request.auth في
Cloud Storage Security Rules.
يمكنك إنشاء رمز مميز مخصّص باستخدام مدير SDK في Firebase، أو يمكنك استخدام مكتبة JWT تابعة لجهة خارجية إذا كان خادمك مكتوبًا بلغة لا تتوافق معها Firebase بشكلٍ أصلي.
قبل البدء
الرموز المخصّصة هي رموز JWT مميّزة وموقَّعة باستخدام المفتاح الخاص لحساب خدمة Google. هناك عدة طرق لتحديد حساب خدمة Google الذي يجب أن تستخدمه Firebase Admin SDK لتوقيع الرموز المخصّصة:
- استخدام ملف JSON لحساب الخدمة -- يمكن استخدام هذه الطريقة في أي بيئة، ولكنها تتطلب تجميع ملف JSON لحساب الخدمة مع الرمز. يجب توخي الحذر الشديد لضمان عدم اطّلاع جهات خارجية على ملف JSON لحساب الخدمة.
- السماح لـ Admin SDK باكتشاف حساب خدمة -- يمكن استخدام هذه الطريقة في البيئات التي تديرها Google، مثل Google Cloud Functions و App Engine. قد تحتاج إلى ضبط بعض الأذونات الإضافية من خلال وحدة التحكم Google Cloud.
- استخدام رقم تعريف حساب خدمة -- عند استخدام هذه الطريقة في بيئة تديرها Google، سيتم توقيع الرموز المميّزة باستخدام مفتاح حساب الخدمة المحدّد. ومع ذلك، تستخدم هذه الطريقة خدمة ويب بعيدة، وقد تحتاج إلى ضبط أذونات إضافية لحساب الخدمة هذا من خلال Google Cloud Console.
استخدام ملف JSON لحساب الخدمة
تحتوي ملفات JSON لحساب الخدمة على جميع المعلومات المقابلة لحسابات الخدمة (بما في ذلك المفتاح الخاص لـ RSA). ويمكن تنزيلها من الـ Firebase Console. يُرجى اتّباع تعليمات إعداد مدير SDK لمزيد من المعلومات حول كيفية تهيئة مدير SDK باستخدام ملف JSON لحساب الخدمة.
تتلاءم طريقة التهيئة هذه مع مجموعة كبيرة من عمليات نشر مدير SDK. كما أنّها تتيح لـ Admin SDK إنشاء الرموز المخصّصة وتوقيعها محليًا، بدون إجراء أي طلبات من واجهة برمجة التطبيقات البعيدة. العيب الرئيسي في هذا النهج هو أنّه يتطلب تجميع ملف JSON لحساب الخدمة مع الرمز. يُرجى أيضًا العِلم أنّ المفتاح الخاص في ملف JSON لحساب الخدمة هو معلومات حساسة، ويجب توخي الحذر الشديد للحفاظ على سريته. على وجه التحديد، يُرجى الامتناع عن إضافة ملفات JSON لحساب الخدمة إلى نظام التحكّم في الإصدارات العلني.
السماح لـ Admin SDK باكتشاف حساب خدمة
إذا تم نشر الرمز في بيئة تديرها Google، يمكن لـ Admin SDK محاولة اكتشاف وسيلة لتوقيع الرموز المخصّصة تلقائيًا:
إذا تم نشر الرمز في بيئة App Engine العادية للغة Java أو Python أو Go، يمكن لـ Admin SDK استخدام خدمة هوية التطبيق المتوفرة في تلك البيئة لتوقيع الرموز المخصصة. توقِّع خدمة "هوية التطبيق" البيانات باستخدام حساب خدمة توفّره Google App Engine لتطبيقك.
إذا تم نشر الرمز في بيئة مُدارة أخرى (مثل Google Cloud Functions أو Google Compute Engine)، يمكن لـ Firebase Admin SDK اكتشاف سلسلة رقم تعريف حساب خدمة تلقائيًا من خادم البيانات الوصفية المحلي . بعد ذلك، يتم استخدام رقم تعريف حساب الخدمة الذي تم اكتشافه مع خدمة "إدارة الهوية وإمكانية الوصول" لتوقيع الرموز المميّزة عن بُعد.
لاستخدام طرق التوقيع هذه، عليك تهيئة حزمة تطوير البرامج (SDK) باستخدام بيانات الاعتماد التلقائية لتطبيق Google وعدم تحديد سلسلة رقم تعريف حساب الخدمة:
Node.js
initializeApp();
Java
FirebaseApp.initializeApp();
Python
default_app = firebase_admin.initialize_app()
Go
app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
#C
FirebaseApp.Create();
لاختبار الرمز نفسه محليًا، عليك تنزيل ملف JSON لحساب الخدمة وضبط متغيّر البيئة GOOGLE_APPLICATION_CREDENTIALS للإشارة إليه.
إذا كان على مدير SDK في Firebase اكتشاف سلسلة رقم تعريف حساب خدمة، يتم ذلك عندما ينشئ الرمز رمزًا مخصّصًا للمرة الأولى. يتم تخزين النتيجة مؤقتًا وإعادة استخدامها لعمليات توقيع الرموز اللاحقة. عادةً ما يكون رقم تعريف حساب الخدمة الذي يتم اكتشافه تلقائيًا أحد حسابات الخدمة التلقائية التي توفّرها Google Cloud
تمامًا كما هو الحال مع أرقام تعريف حسابات الخدمة المحدّدة بشكلٍ صريح، يجب أن يكون لأرقام تعريف حسابات الخدمة التي يتم اكتشافها تلقائيًا إذن iam.serviceAccounts.signBlob لكي يعمل إنشاء الرمز المخصّص. قد تحتاج إلى استخدام قسم
إدارة الهوية وإمكانية الوصول والمشرف في
Google Cloud Console لمنح حسابات الخدمة التلقائية
الأذونات اللازمة. يُرجى الاطّلاع على قسم تحديد المشاكل وحلّها أدناه لمزيد من التفاصيل.
استخدام رقم تعريف حساب خدمة
للحفاظ على الاتساق بين أجزاء مختلفة من تطبيقك، يمكنك تحديد رقم تعريف حساب خدمة سيتم استخدام مفاتيحه لتوقيع الرموز المميّزة عند التشغيل في بيئة تديرها Google. يمكن أن يؤدي ذلك إلى تبسيط سياسات "إدارة الهوية وإمكانية الوصول" وزيادة أمانها، وتجنُّب الحاجة إلى تضمين ملف JSON لحساب الخدمة في الرمز.
يمكن العثور على رقم تعريف حساب الخدمة في الـ
Google Cloud console،
أو في الحقل client_email لملف JSON تم تنزيله لحساب الخدمة.
أرقام تعريف حسابات الخدمة هي عناوين بريد إلكتروني بالتنسيق التالي:
<client-id>@<project-id>.iam.gserviceaccount.com. تحدّد هذه الأرقام حسابات الخدمة بشكلٍ فريد في مشاريع Firebase وGoogle Cloud
لإنشاء رموز مخصّصة باستخدام رقم تعريف حساب خدمة منفصل، عليك تهيئة حزمة تطوير البرامج (SDK) كما هو موضّح أدناه:
Node.js
initializeApp({
serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});
Java
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setServiceAccountId("my-client-id@my-project-id.iam.gserviceaccount.com")
.build();
FirebaseApp.initializeApp(options);
Python
options = {
'serviceAccountId': 'my-client-id@my-project-id.iam.gserviceaccount.com',
}
firebase_admin.initialize_app(options=options)
Go
conf := &firebase.Config{
ServiceAccountID: "my-client-id@my-project-id.iam.gserviceaccount.com",
}
app, err := firebase.NewApp(context.Background(), conf)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
#C
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.GetApplicationDefault(),
ServiceAccountId = "my-client-id@my-project-id.iam.gserviceaccount.com",
});
أرقام تعريف حسابات الخدمة ليست معلومات حساسة، وبالتالي فإنّ اطّلاع الآخرين عليها ليس له أي تأثير. ومع ذلك، لتوقيع الرموز المخصّصة باستخدام حساب الخدمة المحدّد، يجب أن تستدعي Firebase Admin SDK خدمة بعيدة.
علاوةً على ذلك، يجب أيضًا التأكّد من أنّ حساب الخدمة الذي تستخدمه مدير SDK لإجراء هذا الطلب، وعادةً ما يكون {project-name}@appspot.gserviceaccount.com، لديه إذن iam.serviceAccounts.signBlob
permission.
يُرجى الاطّلاع على قسم تحديد المشاكل وحلّها أدناه لمزيد من التفاصيل.
إنشاء رموز مخصّصة باستخدام Firebase Admin SDK
يتضمّن مدير SDK في Firebase طريقة مضمّنة لإنشاء رموز مميّزة مخصّصة. عليك توفير uid على الأقل، ويمكن أن يكون أي سلسلة ولكن يجب أن تحدّد بشكلٍ فريد المستخدم أو الجهاز الذي تتم مصادقته. تنتهي صلاحية هذه الرموز بعد ساعة واحدة.
Node.js
const uid = 'some-uid';
getAuth()
.createCustomToken(uid)
.then((customToken) => {
// Send token back to client
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
Java
String uid = "some-uid";
String customToken = FirebaseAuth.getInstance().createCustomToken(uid);
// Send token back to client
Python
uid = 'some-uid'
custom_token = auth.create_custom_token(uid)
Go
client, err := app.Auth(context.Background())
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
token, err := client.CustomToken(ctx, "some-uid")
if err != nil {
log.Fatalf("error minting custom token: %v\n", err)
}
log.Printf("Got custom token: %v\n", token)
#C
var uid = "some-uid";
string customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
// Send token back to client
يمكنك أيضًا تحديد مطالبات إضافية بشكلٍ اختياري لتضمينها في الرمز المخصّص. على سبيل المثال، أدناه، تمت إضافة حقل premiumAccount إلى الرمز المخصّص، وسيكون متاحًا في العنصرَين auth وrequest.auth في "قواعد الأمان":
Node.js
const userId = 'some-uid';
const additionalClaims = {
premiumAccount: true,
};
getAuth()
.createCustomToken(userId, additionalClaims)
.then((customToken) => {
// Send token back to client
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
Java
String uid = "some-uid";
Map<String, Object> additionalClaims = new HashMap<String, Object>();
additionalClaims.put("premiumAccount", true);
String customToken = FirebaseAuth.getInstance()
.createCustomToken(uid, additionalClaims);
// Send token back to client
Python
uid = 'some-uid'
additional_claims = {
'premiumAccount': True
}
custom_token = auth.create_custom_token(uid, additional_claims)
Go
client, err := app.Auth(context.Background())
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
claims := map[string]interface{}{
"premiumAccount": true,
}
token, err := client.CustomTokenWithClaims(ctx, "some-uid", claims)
if err != nil {
log.Fatalf("error minting custom token: %v\n", err)
}
log.Printf("Got custom token: %v\n", token)
#C
var uid = "some-uid";
var additionalClaims = new Dictionary<string, object>()
{
{ "premiumAccount", true },
};
string customToken = await FirebaseAuth.DefaultInstance
.CreateCustomTokenAsync(uid, additionalClaims);
// Send token back to client
أسماء الرموز المخصّصة المحجوزة
تسجيل الدخول باستخدام رموز مخصّصة على أجهزة العملاء
بعد إنشاء رمز مخصّص، عليك إرساله إلى تطبيق العميل. يُجري تطبيق العميل المصادقة باستخدام الرمز المخصّص من خلال استدعاء signInWithCustomToken():
iOS+
Objective-C
[[FIRAuth auth] signInWithCustomToken:customToken
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
// ...
}];
Swift
Auth.auth().signIn(withCustomToken: customToken ?? "") { user, error in
// ...
}
Android
mAuth.signInWithCustomToken(mCustomToken)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCustomToken:success");
FirebaseUser user = mAuth.getCurrentUser();
updateUI(user);
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCustomToken:failure", task.getException());
Toast.makeText(CustomAuthActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}
}
});
Unity
auth.SignInWithCustomTokenAsync(custom_token).ContinueWith(task => {
if (task.IsCanceled) {
Debug.LogError("SignInWithCustomTokenAsync was canceled.");
return;
}
if (task.IsFaulted) {
Debug.LogError("SignInWithCustomTokenAsync encountered an error: " + task.Exception);
return;
}
Firebase.Auth.AuthResult result = task.Result;
Debug.LogFormat("User signed in successfully: {0} ({1})",
result.User.DisplayName, result.User.UserId);
});
C++
firebase::Future<firebase::auth::AuthResult> result =
auth->SignInWithCustomToken(custom_token);
Web
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
Web
import { getAuth, signInWithCustomToken } from "firebase/auth";
const auth = getAuth();
signInWithCustomToken(auth, token)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// ...
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ...
});
إذا نجحت المصادقة، سيتم تسجيل دخول المستخدم إلى تطبيق العميل باستخدام الحساب الذي يحدّده uid المضمّن في الرمز المخصّص. إذا لم يكن هذا الحساب موجودًا من قبل، سيتم إنشاء سجلّ لهذا المستخدم.
بالطريقة نفسها التي تتّبعها طرق تسجيل الدخول الأخرى (مثل
signInWithEmailAndPassword() و signInWithCredential()) سيتم ملء العنصر auth
في Realtime Database Security Rules و
العنصر request.auth في
Cloud Storage Security Rules باستخدام uid الخاص بالمستخدم. في هذه الحالة، سيكون uid هو الذي حدّدته عند إنشاء الرمز المخصّص.
قواعد البيانات
{
"rules": {
"adminContent": {
".read": "auth.uid === 'some-uid'"
}
}
}
قواعد التخزين
service firebase.storage {
match /b/<your-firebase-storage-bucket>/o {
match /adminContent/{filename} {
allow read, write: if request.auth != null && request.auth.uid == "some-uid";
}
}
}
إذا كان الرمز المخصّص يحتوي على مطالبات إضافية، يمكن الإشارة إليها من العنصر
auth.token (Firebase Realtime Database) أو request.auth.token
(Cloud Storage) في قواعدك:
قواعد البيانات
{
"rules": {
"premiumContent": {
".read": "auth.token.premiumAccount === true"
}
}
}
قواعد التخزين
service firebase.storage {
match /b/<your-firebase-storage-bucket>/o {
match /premiumContent/{filename} {
allow read, write: if request.auth.token.premiumAccount == true;
}
}
}
إنشاء رموز مخصّصة باستخدام مكتبة JWT تابعة لجهة خارجية
إذا كانت الواجهة الخلفية بلغة لا تتضمّن Firebase Admin SDK رسميًا، لا يزال بإمكانك إنشاء رموز مخصّصة يدويًا. أولاً، ابحث عن مكتبة JWT تابعة لجهة خارجية للغتك. بعد ذلك، استخدِم مكتبة JWT هذه لإنشاء رمز JWT يتضمّن المطالبات التالية:
| مطالبات الرمز المخصّص | ||
|---|---|---|
alg |
خوارزمية | "RS256" |
iss |
جهة الإصدار | عنوان البريد الإلكتروني لحساب خدمة مشروعك |
sub |
الموضوع | عنوان البريد الإلكتروني لحساب خدمة مشروعك |
aud |
الجمهور | "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit" |
iat |
وقت الإصدار | الوقت الحالي بالثواني منذ بدء حقبة يونكس |
exp |
وقت انتهاء الصلاحية |
الوقت بالثواني منذ بدء حقبة يونكس الذي تنتهي فيه صلاحية الرمز. يمكن أن يكون
3600 ثانية كحد أقصى بعد iat.
ملاحظة: لا يتحكّم هذا الإعداد إلا في الوقت الذي تنتهي فيه صلاحية الرمز المخصّص نفسه تنتهي صلاحيته. ولكن بعد تسجيل دخول المستخدم باستخدام signInWithCustomToken()، سيظل مسجّلاً الدخول إلى
الجهاز إلى أن يتم إبطال جلسته أو يسجّل المستخدم الخروج.
|
uid |
يجب أن يكون المعرّف الفريد للمستخدم الذي تم تسجيل دخوله سلسلة تتراوح بين
حرف واحد و128 حرفًا، بما في ذلك. توفر uid الأقصر أداءً أفضل.
|
|
claims (اختياري) |
مطالبات مخصّصة اختيارية لتضمينها في متغيّرات auth /
request.auth في "قواعد الأمان"
|
|
في ما يلي بعض الأمثلة على كيفية إنشاء رموز مميّزة مخصّصة بلغات متنوعة لا تتوافق معها مدير SDK في Firebase:
PHP
باستخدام php-jwt:
// Requires: composer require firebase/php-jwt
use Firebase\JWT\JWT;
// Get your service account's email address and private key from the JSON key file
$service_account_email = "abc-123@a-b-c-123.iam.gserviceaccount.com";
$private_key = "-----BEGIN PRIVATE KEY-----...";
function create_custom_token($uid, $is_premium_account) {
global $service_account_email, $private_key;
$now_seconds = time();
$payload = array(
"iss" => $service_account_email,
"sub" => $service_account_email,
"aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iat" => $now_seconds,
"exp" => $now_seconds+(60*60), // Maximum expiration time is one hour
"uid" => $uid,
"claims" => array(
"premium_account" => $is_premium_account
)
);
return JWT::encode($payload, $private_key, "RS256");
}
لغة Ruby
باستخدام ruby-jwt:
require "jwt"
# Get your service account's email address and private key from the JSON key file
$service_account_email = "service-account@my-project-abc123.iam.gserviceaccount.com"
$private_key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."
def create_custom_token(uid, is_premium_account)
now_seconds = Time.now.to_i
payload = {:iss => $service_account_email,
:sub => $service_account_email,
:aud => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
:iat => now_seconds,
:exp => now_seconds+(60*60), # Maximum expiration time is one hour
:uid => uid,
:claims => {:premium_account => is_premium_account}}
JWT.encode payload, $private_key, "RS256"
end
بعد إنشاء الرمز المخصّص، أرسِله إلى تطبيق العميل لاستخدامه في المصادقة باستخدام Firebase. يُرجى الاطّلاع على نماذج الرموز أعلاه لمعرفة كيفية إجراء ذلك.
تحديد المشاكل وحلّها
يحدّد هذا القسم بعض المشاكل الشائعة التي قد يواجهها المطوّرون عند إنشاء رموز مخصّصة، وكيفية حلّها.
لم يتم تفعيل IAM API
إذا كنت تحدّد رقم تعريف حساب خدمة لتوقيع الرموز المميّزة، قد يظهر لك خطأ مشابه لما يلي:
Identity and Access Management (IAM) API has not been used in project 1234567890 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=1234567890 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
تستخدم Firebase Admin SDK واجهة برمجة التطبيقات IAM لتوقيع الرموز المميّزة. يشير هذا الخطأ إلى أنّ واجهة برمجة التطبيقات IAM غير مفعّلة حاليًا لمشروعك على Firebase. افتح الرابط في رسالة الخطأ في متصفّح ويب، وانقر على الزر "تفعيل واجهة برمجة التطبيقات" لتفعيلها لمشروعك.
لا يملك حساب الخدمة الأذونات المطلوبة
إذا لم يكن لحساب الخدمة الذي يتم تشغيل Firebase Admin SDK به إذن iam.serviceAccounts.signBlob، قد تظهر لك رسالة خطأ مثل ما يلي:
Permission iam.serviceAccounts.signBlob is required to perform this operation
on service account projects/-/serviceAccounts/{your-service-account-id}.
لحلّ هذه المشكلة، عليك منح دور منشئ الرمز المميّز لحساب الخدمة في "إدارة الهوية وإمكانية الوصول" لحساب الخدمة المناسب. يعتمد حساب الخدمة التلقائي المستخدَم على البيئة وإصدار Cloud Functions:
- Cloud Functions (الجيل الأول): يستخدم حساب خدمة App Engine التلقائي.
- Cloud Functions (الجيل الثاني): يستخدم حساب خدمة Compute Engine التلقائي.
في Google Cloud Console، انتقِل إلى إدارة الهوية وإمكانية الوصول.
انقر على رمز التعديل المقابل لحساب الخدمة الذي تريد تعديله.
انقر على إضافة دور آخر.
اكتب
"Service Account Token Creator"في فلتر البحث، واختَره من النتائج.انقر على حفظ لتأكيد منح الدور.
يُرجى الرجوع إلى مستندات "إدارة الهوية وإمكانية الوصول" لمزيد من التفاصيل حول هذه العملية، أو تعرَّف على كيفية تعديل الأدوار باستخدام أدوات سطر الأوامر gcloud.
تعذّر تحديد حساب الخدمة
إذا ظهرت لك رسالة خطأ مشابهة لما يلي، لم يتم تهيئة Firebase Admin SDK بشكلٍ صحيح.
Failed to determine service account ID. Initialize the SDK with service account credentials or specify a service account ID with iam.serviceAccounts.signBlob permission.
إذا كنت تعتمد على حزمة تطوير البرامج (SDK) لاكتشاف رقم تعريف حساب خدمة تلقائيًا، تأكَّد من نشر الرمز في بيئة مُدارة من Google تتضمّن خادم بيانات وصفية. بخلاف ذلك، تأكَّد من تحديد ملف JSON لحساب الخدمة أو رقم تعريف حساب الخدمة عند تهيئة حزمة تطوير البرامج (SDK).