إنشاء الرموز المميّزة المخصّصة

تمنحك Firebase تحكّمًا كاملاً في المصادقة من خلال السماح لك بمصادقة المستخدمين أو الأجهزة باستخدام رموز JSON Web Tokens (JWTs) آمنة. يمكنك إنشاء هذه الرموز المميّزة على خادمك، وإعادتها إلى جهاز عميل، ثم استخدام ها للمصادقة من خلال طريقة 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 لحساب الخدمة إلى نظام التحكّم في الإصدارات العلني.

السماح لـ مدير SDK باكتشاف حساب الخدمة

إذا تم نشر الرمز في بيئة تديرها Google، يمكن لـ Admin SDK محاولة الاكتشاف التلقائي لوسيلة توقيع الرموز المخصّصة:

  • إذا تم نشر الرمز في بيئة App Engine العادية للغة Java أو Python أو Go، يمكن لـ Admin SDK استخدام خدمة App Identity المتوفّرة في تلك البيئة لتوقيع الرموز المخصّصة. توقِّع خدمة App Identity البيانات باستخدام حساب خدمة تم توفيره لتطبيقك من قِبل Google App Engine.

  • إذا تم نشر الرمز في بيئة مُدارة أخرى (مثل Google Cloud Functions أو Google Compute Engine)، يمكن لـ مدير SDK في Firebase الاكتشاف التلقائي لـ سلسلة رقم تعريف حساب الخدمة من خادم البيانات الوصفية المحلي. بعد ذلك، يتم استخدام رقم تعريف حساب الخدمة الذي تم اكتشافه جنبًا إلى جنب مع خدمة IAM لتوقيع الرموز المميّزة عن بُعد.

لاستخدام طرق التوقيع هذه، عليك تهيئة حزمة تطوير البرامج (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 لكي ينجح إنشاء الرمز المخصّص. قد تحتاج إلى استخدام قسم IAM والمشرف في Google Cloud Console لمنح حسابات الخدمة التلقائية الأذونات اللازمة. يُرجى الاطّلاع على قسم تحديد المشاكل وحلّها أدناه لمزيد من التفاصيل.

استخدام رقم تعريف حساب خدمة

للحفاظ على الاتساق بين أجزاء مختلفة من تطبيقك، يمكنك تحديد رقم تعريف حساب خدمة سيتم استخدام مفاتيحه لتوقيع الرموز المميّزة عند التشغيل في بيئة تديرها Google. يمكن أن يؤدي ذلك إلى تبسيط سياسات IAM وزيادة أمانها، وتجنُّب الحاجة إلى تضمين ملف 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 API غير مفعّلة حاليًا لمشروعك على 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}.

لحلّ هذه المشكلة، عليك منح دور IAM منشئ الرمز المميّز لحساب الخدمة لحساب الخدمة المناسب. يعتمد حساب الخدمة التلقائي المستخدَم على البيئة وإصدار Cloud Functions:

  • Cloud Functions (الجيل الأول): يستخدم حساب خدمة App Engine التلقائي.
  • Cloud Functions (الجيل الثاني): يستخدم حساب خدمة Compute Engine التلقائي.
  1. في Google Cloud Console، انتقِل إلى إدارة الهوية وإمكانية الوصول (IAM).

  2. انقر على رمز التعديل المقابل لحساب الخدمة الذي تريد تعديله.

  3. انقر على إضافة دور آخر.

  4. اكتب "Service Account Token Creator" في فلتر البحث، واختَره من النتائج.

  5. انقر على حفظ لتأكيد منح الدور.

يُرجى الرجوع إلى مستندات IAM لمزيد من التفاصيل حول هذه العملية، أو تعرَّف على كيفية تعديل الأدوار باستخدام أدوات سطر الأوامر 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).