Firebase предоставляет полный контроль над аутентификацией, позволяя аутентифицировать пользователей или устройства с помощью защищенных JSON Web Token (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.
Вы можете создать собственный токен с помощью Firebase Admin SDK или сторонней библиотеки JWT, если ваш сервер написан на языке, который не поддерживается Firebase.
Подготовка
Специальные токены – это подписанные JWT, в которых закрытый ключ, используемый для подписи, принадлежит сервисному аккаунту Google. Указать сервисный аккаунт Google, который должен использоваться Firebase Admin SDK для подписи специальных токенов, можно несколькими способами:
- С помощью файла JSON сервисного аккаунта. Этот метод можно использовать в любой среде, но для этого вам нужно упаковать файл JSON сервисного аккаунта вместе с кодом. Необходимо принять меры, чтобы JSON-файл сервисного аккаунта не попал в руки посторонних лиц.
- Как разрешить Admin SDK обнаруживать сервисный аккаунт Этот метод можно использовать в средах, управляемых Google, например в функциях Google Cloud и App Engine. Возможно, вам потребуется настроить дополнительные разрешения в консоли Google Cloud.
- С помощью идентификатора сервисного аккаунта – при использовании в управляемой Google среде этот метод подписывает токены с помощью ключа указанного сервисного аккаунта. Однако он использует удаленный веб-сервис, и вам может потребоваться настроить дополнительные разрешения для этого сервисного аккаунта через консоль Google Cloud.
Как использовать файл JSON сервисного аккаунта
JSON-файлы сервисных аккаунтов содержат всю информацию, относящуюся к сервисным аккаунтам (в том числе закрытый ключ RSA). Их можно скачать из консоли Firebase. Подробнее о том, как инициализировать Admin SDK с помощью JSON-файла сервисного аккаунта, рассказывается в инструкциях по настройке Admin SDK.
Этот способ инициализации подходит для большинства развертываний Admin SDK. Кроме того, он позволяет Admin SDK создавать и подписывать собственные токены локально, не выполняя удаленные вызовы API. Основной недостаток этого подхода заключается в том, что вам нужно упаковать JSON-файл сервисного аккаунта вместе с кодом. Обратите внимание, что закрытый ключ в JSON-файле сервисного аккаунта – это конфиденциальная информация, которую необходимо защищать. В частности, не добавляйте JSON-файлы сервисных аккаунтов в общедоступные системы управления версиями.
Как разрешить Admin SDK обнаруживать сервисный аккаунт
Если ваш код развернут в среде, управляемой Google, Admin SDK может попытаться автоматически обнаружить способ подписания специальных токенов:
Если ваш код развернут в стандартной среде App Engine для Java, Python или Go, Admin SDK может использовать сервис идентификации приложений, присутствующий в этой среде, для подписи специальных токенов. Сервис идентификации приложений подписывает данные с помощью сервисного аккаунта, предоставленного Google App Engine для вашего приложения.
Если ваш код развернут в другой управляемой среде (например, в функциях Google Cloud или Google Compute Engine), Firebase Admin SDK может автоматически обнаружить строку идентификатора сервисного аккаунта на локальном сервере метаданных. Обнаруженный идентификатор сервисного аккаунта используется вместе с сервисом IAM для удаленной подписи токенов.
Чтобы использовать эти методы подписи, инициализируйте SDK с учетными данными Google Application Default и не указывайте строку идентификатора сервисного аккаунта:
Node.js
initializeApp();
Java
FirebaseApp.initializeApp();
Python
default_app = firebase_admin.initialize_app()
Проложить маршрут
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 путь к нему.
Если Firebase Admin SDK нужно обнаружить строку идентификатора сервисного аккаунта, это происходит, когда ваш код впервые создает специальный токен. Результат кешируется и используется для последующих операций подписания токена. Идентификатор автоматически обнаруженного сервисного аккаунта обычно является одним из сервисных аккаунтов по умолчанию, предоставленных Google Cloud:
Как и в случае с явно указанными идентификаторами сервисных аккаунтов, автоматически обнаруженные идентификаторы сервисных аккаунтов должны иметь разрешение iam.serviceAccounts.signBlob, чтобы можно было создать специальный токен. Возможно, вам потребуется использовать раздел IAM и администрирование консоли Google Cloud, чтобы предоставить сервисным аккаунтам по умолчанию необходимые разрешения. Подробная информация приведена в разделе "Устранение неполадок" ниже.
Как использовать идентификатор сервисного аккаунта
Чтобы обеспечить согласованность между разными частями приложения, вы можете указать идентификатор сервисного аккаунта, ключи которого будут использоваться для подписи токенов при работе в среде, управляемой Google. Это упрощает и повышает безопасность правил IAM, а также позволяет не включать в код JSON-файл сервисного аккаунта.
Идентификатор сервисного аккаунта можно найти в консоли Google Cloud или в поле 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)
Проложить маршрут
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 должен вызвать удаленный сервис.
Кроме того, убедитесь, что у сервисного аккаунта, который Admin SDK использует для выполнения этого вызова (обычно {project-name}@appspot.gserviceaccount.com), есть iam.serviceAccounts.signBlob
разрешение.
Подробная информация приведена в разделе "Устранение неполадок" ниже.
Как создавать собственные токены с помощью Firebase Admin SDK
В Firebase Admin SDK есть встроенный метод для создания специальных токенов. Вам нужно предоставить хотя бы 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)
Проложить маршрут
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)
Проложить маршрут
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, который будет содержать следующие утверждения:
| Специальные утверждения токенов | ||
|---|---|---|
alg |
Алгоритм | "RS256" |
iss |
Издатель | Адрес электронной почты сервисного аккаунта вашего проекта |
sub |
Субъект | Адрес электронной почты сервисного аккаунта вашего проекта |
aud |
Аудитория | "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit" |
iat |
Время выпуска | Текущее время в секундах с начала эпохи UNIX |
exp |
Срок действия |
Время в секундах с начала эпохи UNIX, когда истекает срок действия токена. Она может быть максимум на 3600 секунд позже, чем iat.
Примечание. Это время истечения срока действия специального токена. Но после того, как вы выполните вход пользователя с помощью signInWithCustomToken(), он останется на устройстве до тех пор, пока его сеанс не станет недействительным или пользователь не выйдет из аккаунта.
|
uid |
Уникальный идентификатор пользователя, выполнившего вход, должен быть строкой длиной от 1 до 128 символов включительно. Чем короче uid, тем лучше.
|
|
claims (необязательно) |
Необязательные пользовательские утверждения, которые нужно включить в переменные правил безопасности auth / request.auth
|
|
Ниже приведены примеры того, как создавать собственные токены на разных языках, которые не поддерживаются Firebase Admin SDK:
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. Примеры кода приведены выше.
Устранение неполадок
В этом разделе описаны распространенные проблемы, с которыми могут столкнуться разработчики при создании собственных токенов, и способы их решения.
API IAM не включен
Если вы указываете идентификатор сервисного аккаунта для подписи токенов, может появиться ошибка, похожая на следующую:
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 API для подписи токенов. Эта ошибка означает, что API IAM в настоящее время не включен для вашего проекта Firebase. Откройте ссылку из сообщения об ошибке в браузере и нажмите кнопку "Включить API", чтобы активировать его для своего проекта.
У сервисного аккаунта нет необходимых разрешений
Если у сервисного аккаунта, от имени которого выполняется 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 по умолчанию.
В консоли Google Cloud выберите IAM и администрирование.
Нажмите на значок редактирования рядом с нужным сервисным аккаунтом.
Нажмите Добавить другую роль.
Введите
"Service Account Token Creator"в фильтр поиска и выберите его в результатах.Чтобы подтвердить предоставление роли, нажмите Сохранить.
Подробную информацию об этом процессе можно найти в документации по 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 с сервером метаданных. В противном случае при инициализации SDK необходимо указать JSON-файл сервисного аккаунта или его идентификатор.