Criar tokens personalizados

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. uids 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:

  1. Abra o IAM e administrador. no console do Google Cloud.
  2. Selecione seu projeto e clique em "Continuar".
  3. Clique no ícone de edição correspondente à conta de serviço que você quer atualizar.
  4. Clique em "Adicionar novo papel".
  5. Digite "Criador de token de conta de serviço" no filtro de pesquisa e selecione-o nos resultados.
  6. 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.