Firebase предоставляет вам полный контроль над аутентификацией, позволяя аутентифицировать пользователей или устройства с помощью защищенных JSON Web Tokens (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 Functions и 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 может использовать службу App Identity, присутствующую в этой среде, для подписи пользовательских токенов. Служба App Identity подписывает данные, используя учётную запись службы, предоставленную вашему приложению Google App Engine.
Если ваш код развёрнут в другой управляемой среде (например, Google Cloud Functions, Google Compute Engine), Firebase Admin SDK может автоматически обнаружить строку идентификатора учётной записи службы на локальном сервере метаданных . Обнаруженный идентификатор учётной записи службы затем используется совместно со службой IAM для удалённой подписи токенов.
Чтобы использовать эти методы подписи, инициализируйте SDK с учетными данными Google Application Default и не указывайте строку идентификатора учетной записи службы:
initializeApp();
FirebaseApp.initializeApp();
default_app = firebase_admin.initialize_app()
app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
FirebaseApp.Create();
Чтобы протестировать тот же код локально, загрузите JSON-файл учетной записи службы и установите переменную среды GOOGLE_APPLICATION_CREDENTIALS
так, чтобы она указывала на него.
Если Firebase Admin SDK необходимо обнаружить строку идентификатора учётной записи службы, это происходит при первом создании пользовательского токена вашим кодом. Результат кэшируется и используется повторно для последующих операций подписи токенов. Автоматически обнаруженный идентификатор учётной записи службы обычно представляет собой одну из учётных записей службы по умолчанию, предоставляемых Google Cloud :
- Учетная запись службы Compute Engine по умолчанию
- Учетная запись службы Cloud Functions по умолчанию
Как и в случае с явно указанными идентификаторами учётных записей служб, для создания собственных токенов автоматически обнаруженные идентификаторы учётных записей служб должны иметь разрешение 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, как показано ниже:
initializeApp({
serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setServiceAccountId("my-client-id@my-project-id.iam.gserviceaccount.com")
.build();
FirebaseApp.initializeApp(options);
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)
}
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
, который может быть любой строкой, но должен однозначно идентифицировать пользователя или устройство, для которого вы проходите аутентификацию. Срок действия токенов истекает через час.
const uid = 'some-uid';
getAuth()
.createCustomToken(uid)
.then((customToken) => {
// Send token back to client
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
String uid = "some-uid";
String customToken = FirebaseAuth.getInstance().createCustomToken(uid);
// Send token back to client
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)
var uid = "some-uid";
string customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
// Send token back to client
Вы также можете указать дополнительные требования для включения в пользовательский токен. Например, ниже показано, как в пользовательский токен было добавлено поле premiumAccount
, которое будет доступно в объектах auth
/ request.auth
в ваших правилах безопасности:
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);
});
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
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)
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()
:
Objective-C
[[FIRAuth auth] signInWithCustomToken:customToken
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
// ...
}];
Быстрый
Auth.auth().signIn(withCustomToken: customToken ?? "") { user, error in
// ...
}
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);
}
}
});
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);
});
firebase::Future<firebase::auth::AuthResult> result =
auth->SignInWithCustomToken(custom_token);
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
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-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-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 для подписи токенов. Эта ошибка означает, что IAM API в настоящее время не включён для вашего проекта 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-роль «Создатель токена учетной записи службы» соответствующей учетной записи службы, обычно {project-name}@appspot.gserviceaccount.com
:
- Откройте страницу IAM и администрирования в консоли Google Cloud .
- Выберите свой проект и нажмите «Продолжить».
- Щелкните значок редактирования, соответствующий учетной записи службы, которую вы хотите обновить.
- Нажмите «Добавить еще одну роль».
- Введите «Создатель токена учетной записи службы» в фильтр поиска и выберите его из результатов.
- Нажмите «Сохранить», чтобы подтвердить предоставление роли.
Более подробную информацию об этом процессе можно найти в документации 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.