Com o Firebase, você tem controle total sobre as autenticações ao permitir que usuários e dispositivos sejam autenticados usando JSON Web Tokens (JWTs) seguros. Você gera esses tokens no seu servidor, os transmite de volta a um dispositivo cliente e os usa para autenticar por meio do método signInWithCustomToken()
.
Para isso, é necessário criar um endpoint do servidor que aceite credenciais de login (como nome de usuário e senha) e, se as credenciais forem válidas, retorne um JWT personalizado. O JWT personalizado retornado de seu servidor pode ser usado por um dispositivo cliente para autenticação com o Firebase (iOS+, Android, Web). Após a autenticação, essa identidade será
usada para acessar outros serviços do Firebase, como o Firebase Realtime Database
e o Cloud Storage. Além disso, o conteúdo do JWT será
disponíveis no objeto auth
da
Realtime Database Security Rules e as
request.auth
na sua
Cloud Storage Security Rules
É possível criar um token personalizado com o SDK Admin do Firebase ou você poderá usar uma biblioteca JWT de terceiros caso seu servidor esteja escrito em uma linguagem que não tem suporte nativo no Firebase.
Antes de começar
Os tokens personalizados são JWTs assinados em que a chave privada, usada para assinatura, é de uma conta de serviço do Google. Existem várias maneiras de especificar a conta de serviço do Google que deve ser usada pelo SDK Admin do Firebase para assinar tokens personalizados:
- Com o uso de um arquivo JSON da conta de serviço — Esse método pode ser usado em qualquer ambiente, mas exige que você empacote um arquivo JSON da conta de serviço com seu código. É preciso tomar cuidado para garantir que o arquivo JSON da conta de serviço não seja exposto a partes externas.
- Permitindo a descoberta de uma conta de serviço pelo SDK Admin ‒ Esse método pode ser usado em ambientes gerenciados pelo Google, como o Google Cloud Functions e o App Engine. Talvez seja necessário configurar alguns permissões adicionais por meio do console do Google Cloud.
- Usando um ID de conta de serviço — Quando usado em um ambiente gerenciado pelo Google, esse método assinará tokens usando a chave da conta de serviço especificada. No entanto, ele usa um serviço da Web remoto e você pode precisar configurar permissões adicionais para essa conta de serviço por meio do Console do Google Cloud.
Com o uso de um arquivo JSON da conta de serviço
Os arquivos JSON da conta de serviço têm todas as informações correspondentes às contas de serviço, incluindo a chave privada RSA. É possível fazer o download deles no Console do Firebase. Para mais informações sobre como inicializar o SDK Admin com um arquivo JSON da conta de serviço, siga as Instruções de configuração do SDK Admin.
Esse método de inicialização é adequado para uma grande variedade de implantações do SDK Admin. Com ele, também é possível usar o SDK Admin para criar e assinar tokens personalizados localmente, sem fazer chamadas remotas de API. A principal desvantagem dessa abordagem é que você precisa empacotar um arquivo JSON da conta de serviço com seu código. Observe também que a chave privada em um arquivo JSON da conta de serviço é uma informação sensível. É preciso tomar cuidado especial para mantê-la confidencial. Especificamente, evite adicionar arquivos JSON da conta de serviço ao controle de versão pública.
Permitindo a descoberta de uma conta de serviço pelo SDK Admin
Se o código for implantado em um ambiente gerenciado pelo Google, é possível que o SDK Admin tente descobrir automaticamente um meio de assinar tokens personalizados:
Se o código for implantado no ambiente padrão do App Engine para Java, Python ou Go, o SDK Admin poderá usar o serviço App Identity presente nesse ambiente para assinar tokens personalizados. O serviço App Identity assina dados usando uma conta de serviço fornecida para seu aplicativo pelo Google App Engine.
Se seu código for implantado em outro ambiente gerenciado (por exemplo, Google Cloud Functions, Google Compute Engine), o SDK Admin do Firebase poderá detectar automaticamente uma string de ID da conta de serviço no servidor de metadados local. O ID da conta de serviço descoberto é então usado com o serviço IAM para assinar os tokens remotamente.
Para usar esses métodos de assinatura, inicialize o SDK com o Google Application Default Credentials e não especifique uma string de ID de conta de serviço:
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();
Para testar o mesmo código localmente, faça o download de um arquivo JSON da conta de serviço e defina a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS
para apontar para ela.
Se o SDK Admin do Firebase precisar descobrir uma string de ID de conta de serviço, isso será feito quando seu código criar um token personalizado pela primeira vez. O resultado é armazenado em cache e reutilizado para operações subsequentes de assinatura de token. O ID da conta de serviço descoberta automaticamente geralmente é uma das contas de serviço padrão fornecidas pelo Google Cloud:
Assim como com os IDs de conta de serviço especificados explicitamente, os IDs de conta de serviço descobertos automaticamente precisam ter a permissão iam.serviceAccounts.signBlob
para que a criação do token personalizado funcione. Talvez seja necessário usar a seção IAM e Admin do Console do Google Cloud para conceder as permissões necessárias às contas de serviço padrão. Para mais informações, consulte a seção de solução de problemas abaixo.
Usando um ID de conta de serviço
Para manter a consistência entre várias partes do seu aplicativo, é possível especificar um ID de conta de serviço cujas chaves serão usadas para assinar os tokens quando executados em um ambiente gerenciado pelo Google. Isso pode tornar as políticas do IAM mais simples e mais seguras e evitar a necessidade de incluir o arquivo JSON da conta de serviço no seu código.
O ID da conta de serviço pode ser encontrado no Console do Google Cloud ou no campo client_email
de um arquivo JSON de conta de serviço salvo.
IDs de contas de serviço são endereços de e-mail com o seguinte formato: <client-id>@<project-id>.iam.gserviceaccount.com
. Eles identificam de forma exclusiva as contas de serviço nos projetos do Firebase e do Google Cloud.
Para criar tokens personalizados usando um ID de conta de serviço separado, inicialize o SDK conforme mostrado abaixo:
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",
});
IDs de contas de serviço não são informações sensíveis. Portanto, a exposição desses dados é irrelevante. No entanto, para assinar tokens personalizados com a conta de serviço especificada, o SDK Admin do Firebase precisa invocar um serviço remoto.
Além disso, você também precisa verificar se a conta de serviço que o SDK Admin está usando para fazer essa chamada, normalmente {project-name}@appspot.gserviceaccount.com
, tem a permissão iam.serviceAccounts.signBlob
.
Para mais informações, consulte a seção de solução de problemas abaixo.
Criar tokens personalizados usando o SDK Admin do Firebase
O SDK Admin do Firebase tem um método incorporado para criação de tokens personalizados. No mínimo, você precisa fornecer um uid
, que pode ser qualquer string, mas precisa identificar exclusivamente o usuário ou dispositivo que você está autenticando. Esses tokens expiram após uma hora.
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
Você também pode especificar outras declarações a serem incluídas no token personalizado. Por exemplo, abaixo, um campo premiumAccount
foi adicionado ao token personalizado, que estará disponível nos objetos auth
/request.auth
das Regras de segurança:
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
Nomes de token personalizados reservados
Fazer login usando tokens personalizados em clientes
Depois de criar um token personalizado, você precisa enviá-lo ao aplicativo cliente. O aplicativo cliente autentica com o token personalizado chamando 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;
// ...
});
Se a autenticação for bem-sucedida, o usuário será conectado ao aplicativo cliente com a conta especificada pelo uid
incluído no token personalizado. Se essa conta ainda não tiver sido criada, um registro será adicionado para o usuário.
Assim como em outros métodos de login (como
signInWithEmailAndPassword()
e signInWithCredential()
), o objeto auth
nos seus Realtime Database Security Rules e
o objeto request.auth
na sua
Cloud Storage Security Rules será
preenchido com uid
do usuário. Nesse caso, o uid
será aquele que
você especificou ao gerar o token personalizado.
Regras do Database
{
"rules": {
"adminContent": {
".read": "auth.uid === 'some-uid'"
}
}
}
Regras do Storage
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";
}
}
}
Se o token personalizado tiver outras declarações, elas poderão ser referenciadas fora dos objetos auth.token
(Firebase Realtime Database) ou request.auth.token
(Cloud Storage) nas regras:
Regras do Database
{
"rules": {
"premiumContent": {
".read": "auth.token.premiumAccount === true"
}
}
}
Regras do Storage
service firebase.storage {
match /b/<your-firebase-storage-bucket>/o {
match /premiumContent/{filename} {
allow read, write: if request.auth.token.premiumAccount == true;
}
}
}
Criar tokens personalizados usando uma biblioteca JWT de terceiros
Se seu back-end estiver em uma linguagem que não contenha o SDK Admin oficial do Firebase, ainda será possível criar tokens personalizados manualmente. Primeiro, encontre uma biblioteca JWT de terceiros para sua linguagem. Em seguida, use essa biblioteca JWT para criar um JWT que inclua as seguintes declarações:
Declarações de tokens personalizados | ||
---|---|---|
alg |
Algoritmo | "RS256" |
iss |
Emissor | Endereço de e-mail da conta de serviço do seu projeto |
sub |
Assunto | Endereço de e-mail da conta de serviço do seu projeto |
aud |
Público-alvo | "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit" |
iat |
Hora de emissão | A hora atual, em segundos, desde a época do UNIX |
exp |
Data de validade |
O tempo, em segundos, desde a época do UNIX, em que o token expira. Pode ser no máximo 3.600 segundos depois de iat .
Observação: ele controla o tempo apenas quando o token personalizado expira. No entanto, quando você faz o login de um usuário utilizando signInWithCustomToken() , ele permanece conectado ao
dispositivo até que a sessão seja invalidada ou que o usuário se desconecte.
|
uid |
O identificador exclusivo do usuário que fez login precisa ser uma string entre
1 e 128 caracteres. uid s mais curtos oferecem um desempenho
melhor.
|
|
claims (opcional) |
Declarações personalizadas opcionais a serem incluídas nas variáveis auth /request.auth das Regras de segurança |
Veja abaixo alguns exemplos sobre como criar tokens personalizados em várias linguagens não compatíveis com o SDK Admin do Firebase:
PHP
Como usar 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
Como usar 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
Depois de criar o token personalizado, envie-o para o aplicativo cliente para usar na autenticação com o Firebase. Consulte as amostras de códigos acima para saber como fazer isso.
Solução de problemas
Nesta seção, descrevemos alguns problemas comuns que os desenvolvedores podem encontrar na criação de tokens personalizados e como resolvê-los.
API IAM não ativada
Se você estiver especificando um ID de conta de serviço para assinar tokens, poderá receber um erro semelhante ao seguinte:
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.
O SDK Admin do Firebase usa a API IAM para assinar tokens. Esse erro indica que a API IAM não está ativada no momento para seu projeto do Firebase. Abra o link na mensagem de erro em um navegador da Web e clique no botão "Ativar API" para ativá-la no projeto.
A conta de serviço não tem as permissões necessárias
Se a conta de serviço que o SDK Admin do Firebase estiver executando não tiver a permissão iam.serviceAccounts.signBlob
, você poderá receber uma mensagem de erro como esta:
Permission iam.serviceAccounts.signBlob is required to perform this operation on service account projects/-/serviceAccounts/{your-service-account-id}.
A maneira mais fácil de resolver esse problema é conceder o papel de IAM de "Criador de token de conta de serviço" à conta de serviço em questão, geralmente {project-name}@appspot.gserviceaccount.com
:
- Abra o IAM e administrador. no console do Google Cloud.
- Selecione seu projeto e clique em "Continuar".
- Clique no ícone de edição correspondente à conta de serviço que você quer atualizar.
- Clique em "Adicionar novo papel".
- Digite "Criador de token de conta de serviço" no filtro de pesquisa e selecione-o nos resultados.
- Clique em "Salvar" para confirmar a concessão do papel.
Consulte a documentação do IAM para mais informações sobre esse processo ou para saber como atualizar papéis com a ferramenta de linha de comando gcloud.
Falha ao determinar a conta de serviço
Se você receber uma mensagem de erro semelhante à seguinte, o SDK Admin do Firebase não foi inicializado corretamente.
Failed to determine service account ID. Initialize the SDK with service account credentials or specify a service account ID with iam.serviceAccounts.signBlob permission.
Se você estiver confiando no SDK para descobrir automaticamente um ID de conta de serviço, verifique se o código foi implantado em um ambiente gerenciado do Google com um servidor de metadados. Caso contrário, não se esqueça de especificar o arquivo JSON da conta de serviço ou o ID da conta de serviço na inicialização do SDK.