Criar tokens personalizados

O Firebase oferece controle total sobre a autenticação, permitindo autenticar usuários ou dispositivos usando JSON Web Tokens (JWTs) seguros. Você gera esses tokens em seu servidor, os transmite de volta para um dispositivo cliente e os usa para autenticar por meio do método signInWithCustomToken() .

Para conseguir isso, você deve criar um endpoint de 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 do seu servidor pode então ser usado por um dispositivo cliente para autenticação com Firebase ( iOS+ , Android , web ). Depois de autenticada, essa identidade será usada ao acessar outros serviços do Firebase, como o Firebase Realtime Database e o Cloud Storage. Além disso, o conteúdo do JWT estará disponível no objeto auth nas suas regras de segurança do Realtime Database e no objeto request.auth nas suas regras de segurança do Cloud Storage .

Você pode criar um token personalizado com o SDK Admin do Firebase ou usar uma biblioteca JWT de terceiros se o seu servidor estiver escrito em uma linguagem que o Firebase não oferece suporte nativo.

Antes de você começar

Os tokens personalizados são JWTs assinados em que a chave privada usada para assinatura pertence a uma conta de serviço do Google. Há 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:

  • Usando um arquivo JSON de conta de serviço : esse método pode ser usado em qualquer ambiente, mas exige que você empacote um arquivo JSON de conta de serviço junto com seu código. Cuidado especial deve ser tomado para garantir que o arquivo JSON da conta de serviço não seja exposto a terceiros.
  • Permitir que o SDK Admin descubra uma conta de serviço : esse método pode ser usado em ambientes gerenciados pelo Google, como Google Cloud Functions e App Engine. Talvez seja necessário configurar algumas 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 pode ser necessário configurar permissões adicionais para esta conta de serviço por meio do console do Google Cloud.

Usando um arquivo JSON de conta de serviço

Os arquivos JSON da conta de serviço contêm todas as informações correspondentes às contas de serviço (incluindo a chave privada RSA). Eles podem ser baixados do console do Firebase. Siga as instruções de configuração do Admin SDK para obter mais informações sobre como inicializar o Admin SDK com um arquivo JSON de conta de serviço.

Esse método de inicialização é adequado para uma ampla variedade de implantações do Admin SDK. Além disso, permite que o Admin SDK crie e assine tokens personalizados localmente, sem fazer chamadas remotas de API. A principal desvantagem dessa abordagem é que ela exige que você empacote um arquivo JSON da conta de serviço junto com seu código. Observe também que a chave privada em um arquivo JSON de conta de serviço é uma informação confidencial e deve-se tomar cuidado especial para mantê-la confidencial. Especificamente, evite adicionar arquivos JSON da conta de serviço ao controle de versão pública.

Permitir que o SDK Admin descubra uma conta de serviço

Se seu código for implantado em um ambiente gerenciado pelo Google, o SDK Admin poderá tentar descobrir automaticamente um meio de assinar tokens personalizados:

  • Se seu código for implantado no ambiente padrão do App Engine para Java, Python ou Go, o Admin SDK 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 algum outro ambiente gerenciado (por exemplo, Google Cloud Functions, Google Compute Engine), o SDK Admin do Firebase poderá descobrir automaticamente uma string de ID de conta de serviço no servidor de metadados local. O ID da conta de serviço descoberto é então usado em conjunto com o serviço IAM para assinar tokens remotamente.

Para usar esses métodos de assinatura, inicialize o SDK com credenciais padrão do aplicativo Google e não especifique uma string de ID de conta de serviço:

Node.js

initializeApp();

Java

FirebaseApp.initializeApp();

Pitão

default_app = firebase_admin.initialize_app()

Ir

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, baixe um arquivo JSON da conta de serviço e defina a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS para apontar para ele.

Se o SDK Admin do Firebase precisar descobrir uma string de ID de conta de serviço, ele fará isso 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 acontece com os IDs de conta de serviço especificados explicitamente, os IDs de conta de serviço descobertos automaticamente devem 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 administrador do console do Google Cloud para conceder às contas de serviço padrão as permissões necessárias. Consulte a seção de solução de problemas abaixo para obter mais detalhes.

Usando um ID de conta de serviço

Para manter a consistência entre as diversas partes do seu aplicativo, você pode especificar um ID de conta de serviço cujas chaves serão usadas para assinar tokens durante a execução em um ambiente gerenciado pelo Google. Isso pode tornar as políticas do IAM mais simples e seguras e evitar a inclusão do 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 da conta de serviço baixado. IDs de conta de serviço são endereços de e-mail que possuem o seguinte formato: <client-id>@<project-id>.iam.gserviceaccount.com . Eles identificam exclusivamente contas de serviço em 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);

Pitão

options = {
    'serviceAccountId': 'my-client-id@my-project-id.iam.gserviceaccount.com',
}
firebase_admin.initialize_app(options=options)

Ir

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",
});

Os IDs das contas de serviço não são informações confidenciais e, portanto, sua exposição é 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 deve certificar-se de que a conta de serviço que o SDK Admin está usando para fazer esta chamada (geralmente {project-name}@appspot.gserviceaccount.com ) tenha a permissão iam.serviceAccounts.signBlob . Consulte a seção de solução de problemas abaixo para obter mais detalhes.

Crie tokens personalizados usando o SDK Admin do Firebase

O SDK Admin do Firebase possui um método integrado para criar tokens personalizados. No mínimo, você precisa fornecer um uid , que pode ser qualquer string, mas deve 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

Pitão

uid = 'some-uid'

custom_token = auth.create_custom_token(uid)

Ir

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

Opcionalmente, você também pode especificar declarações adicionais a serem incluídas no token personalizado. Por exemplo, abaixo, um campo premiumAccount foi adicionado ao token customizado, que estará disponível nos objetos auth / request.auth em suas 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

Pitão

uid = 'some-uid'
additional_claims = {
    'premiumAccount': True
}

custom_token = auth.create_custom_token(uid, additional_claims)

Ir

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 tokens personalizados reservados

Faça login usando tokens personalizados em clientes

Depois de criar um token personalizado, você deverá enviá-lo ao seu aplicativo cliente. O aplicativo cliente autentica com o token personalizado chamando signInWithCustomToken() :

iOS+

Objetivo-C
[[FIRAuth auth] signInWithCustomToken:customToken
                           completion:^(FIRAuthDataResult * _Nullable authResult,
                                        NSError * _Nullable error) {
  // ...
}];
Rápido
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);
                }
            }
        });

Unidade

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);

API com namespace da Web

firebase.auth().signInWithCustomToken(token)
  .then((userCredential) => {
    // Signed in
    var user = userCredential.user;
    // ...
  })
  .catch((error) => {
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

API modular da 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, seu usuário estará conectado ao aplicativo cliente com a conta especificada pelo uid incluído no token personalizado. Se essa conta não existia anteriormente, será criado um registro para esse usuário.

Da mesma forma que outros métodos de login (como signInWithEmailAndPassword() e signInWithCredential() ), o objeto auth nas regras de segurança do Realtime Database e o objeto request.auth nas regras de segurança do Cloud Storage serão preenchidos com o uid do usuário. . Nesse caso, o uid será aquele que você especificou ao gerar o token customizado.

Regras de banco de dados

{
  "rules": {
    "adminContent": {
      ".read": "auth.uid === 'some-uid'"
    }
  }
}

Regras de armazenamento

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 contiver declarações adicionais, elas poderão ser referenciadas no objeto auth.token (Firebase Realtime Database) ou request.auth.token (Cloud Storage) nas suas regras:

Regras de banco de dados

{
  "rules": {
    "premiumContent": {
      ".read": "auth.token.premiumAccount === true"
    }
  }
}

Regras de armazenamento

service firebase.storage {
  match /b/<your-firebase-storage-bucket>/o {
    match /premiumContent/{filename} {
      allow read, write: if request.auth.token.premiumAccount == true;
    }
  }
}

Crie tokens personalizados usando uma biblioteca JWT de terceiros

Se o seu back-end estiver em uma linguagem que não tenha um SDK Admin oficial do Firebase, você ainda poderá criar tokens personalizados manualmente. Primeiro, encontre uma biblioteca JWT de terceiros para o seu idioma. Em seguida, use essa biblioteca JWT para criar um JWT que inclua as seguintes declarações:

Reivindicações de token personalizadas
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 "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Emitido no momento A hora atual, em segundos, desde a época do UNIX
exp Data de validade O tempo, em segundos desde a época UNIX, em que o token expira. Pode ser no máximo 3.600 segundos depois do iat .
Observação: isso controla apenas o horário em que o próprio token personalizado expira. Mas depois que você conectar um usuário usando signInWithCustomToken() , ele permanecerá conectado ao dispositivo até que sua sessão seja invalidada ou o usuário saia.
uid O identificador exclusivo do usuário conectado deve ser uma string com 1 a 128 caracteres, inclusive. uid s mais curtos oferecem melhor desempenho.
claims (opcional) Declarações personalizadas opcionais para incluir nas variáveis auth / request.auth das regras de segurança

Aqui estão alguns exemplos de implementações de como criar tokens personalizados em diversas linguagens não compatíveis com o SDK Admin do Firebase:

PHP

Usando 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");
}

Rubi

Usando 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 ao aplicativo cliente para autenticação no Firebase. Veja os exemplos de código acima para saber como fazer isso.

Solução de problemas

Esta seção descreve alguns problemas comuns que os desenvolvedores podem encontrar ao criar 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 a este:

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. Este erro indica que a API IAM não está habilitada 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 habilitá-lo para o seu 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 está 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 isso é conceder a função IAM "Criador de token de conta de serviço" à conta de serviço em questão, geralmente {project-name}@appspot.gserviceaccount.com :

  1. Abra a página do IAM e do 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ê deseja atualizar.
  4. Clique em "Adicionar outra função".
  5. Digite “Service Account Token Creator” no filtro de pesquisa e selecione-o nos resultados.
  6. Clique em "Salvar" para confirmar a concessão da função.

Consulte a documentação do IAM para obter mais detalhes sobre esse processo ou saiba como atualizar funções usando as ferramentas 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ê depende do SDK para descobrir automaticamente um ID de conta de serviço, verifique se o código está implantado em um ambiente gerenciado do Google com um servidor de metadados. Caso contrário, certifique-se de especificar o arquivo JSON da conta de serviço ou o ID da conta de serviço na inicialização do SDK.