Participe do Firebase Summit on-line e presencialmente em 18 de outubro de 2022. Veja como o Firebase pode ajudar você a acelerar o desenvolvimento de apps, lançar seu aplicativo com confiança e fazer o escalonamento facilmente. Inscreva-se agora

Estenda a autenticação do Firebase com o bloqueio do Cloud Functions

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Se você fez upgrade para o Firebase Authentication com Identity Platform , poderá estender o Firebase Authentication usando o bloqueio do Cloud Functions .

As funções de bloqueio permitem que você execute um código personalizado que modifica o resultado de um usuário se registrar ou fazer login no seu aplicativo. Por exemplo, você pode impedir que um usuário se autentique se ele não atender a determinados critérios ou atualizar as informações de um usuário antes de devolvê-las ao aplicativo cliente.

Antes de você começar

Para usar as funções de bloqueio, você deve atualizar seu projeto do Firebase para o Firebase Authentication with Identity Platform. Se você ainda não atualizou, faça-o primeiro.

Entendendo as funções de bloqueio

Você pode registrar funções de bloqueio para dois eventos:

  • beforeCreate : aciona antes que um novo usuário seja salvo no banco de dados do Firebase Authentication e antes que um token seja retornado ao seu aplicativo cliente.

  • beforeSignIn : aciona depois que as credenciais de um usuário são verificadas, mas antes que o Firebase Authentication retorne um token de ID ao seu aplicativo cliente. Se seu aplicativo usa autenticação multifator, a função é acionada depois que o usuário verifica o segundo fator. Observe que a criação de um novo usuário também aciona beforeSignIn , além de beforeCreate .

Lembre-se do seguinte ao usar funções de bloqueio:

  • Sua função deve responder em 7 segundos. Após 7 segundos, o Firebase Authentication retorna um erro e a operação do cliente falha.

  • Códigos de resposta HTTP diferentes de 200 são passados ​​para seus aplicativos cliente. Certifique-se de que seu código de cliente lide com quaisquer erros que sua função possa retornar.

  • As funções se aplicam a todos os usuários em seu projeto, incluindo qualquer um contido em um locatário . O Firebase Authentication fornece informações sobre os usuários da sua função, incluindo os locatários aos quais eles pertencem, para que você possa responder adequadamente.

  • A vinculação de outro provedor de identidade a uma conta aciona novamente todas as funções de beforeSignIn registradas.

  • A autenticação anônima e personalizada não aciona funções de bloqueio.

Implantar e registrar uma função de bloqueio

Para inserir seu código personalizado nos fluxos de autenticação do usuário, implante e registre funções de bloqueio. Depois que suas funções de bloqueio forem implantadas e registradas, seu código personalizado deverá ser concluído com êxito para que a autenticação e a criação do usuário sejam bem-sucedidas.

Implantar uma função de bloqueio

Você implanta uma função de bloqueio da mesma forma que implanta qualquer função. (consulte a página de primeiros passos do Cloud Functions para obter detalhes). Resumindo:

  1. Escreva Cloud Functions que lidam com o evento beforeCreate , o evento beforeSignIn ou ambos.

    Por exemplo, para começar, você pode adicionar as seguintes funções sem operação ao index.js :

    const functions = require('firebase-functions');
    
    exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
      // TODO
    });
    
    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      // TODO
    });
    

    Os exemplos acima omitiram a implementação da lógica de autenticação personalizada. Consulte as seções a seguir para saber como implementar suas funções de bloqueio e cenários comuns para exemplos específicos.

  2. Implante suas funções usando a Firebase CLI:

    firebase deploy --only functions
    

    Você deve reimplantar suas funções sempre que as atualizar.

Registrar uma função de bloqueio

  1. Acesse a página Configurações de autenticação do Firebase no console do Firebase.

  2. Selecione a guia Funções de bloqueio .

  3. Registre sua função de bloqueio selecionando-a no menu suspenso em Antes da criação da conta (beforeCreate) ou Antes de entrar (beforeSignIn) .

  4. Salve suas alterações.

Obtendo informações de usuário e contexto

Os eventos beforeSignIn e beforeCreate fornecem objetos User e EventContext que contêm informações sobre o usuário que está entrando. Use esses valores em seu código para determinar se uma operação deve continuar.

Para obter uma lista de propriedades disponíveis no objeto User , consulte a referência da API UserRecord .

O objeto EventContext contém as seguintes propriedades:

Nome Descrição Exemplo
locale A localidade do aplicativo. Você pode definir a localidade usando o SDK do cliente ou passando o cabeçalho de localidade na API REST. fr ou sv-SE
ipAddress O endereço IP do dispositivo em que o usuário final está se registrando ou entrando. 114.14.200.1
userAgent O agente do usuário que aciona a função de bloqueio. Mozilla/5.0 (X11; Linux x86_64)
eventId O identificador exclusivo do evento. rWsyPtolplG2TBFoOkkgyg
eventType O tipo de evento. Isso fornece informações sobre o nome do evento, como beforeSignIn ou beforeCreate , e o método de login associado usado, como Google ou email/senha. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Sempre USER . USER
resource O projeto ou locatário do Firebase Authentication. projects/ project-id /tenants/ tenant-id
timestamp A hora em que o evento foi acionado, formatado como uma string RFC 3339 . Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Um objeto que contém informações sobre o usuário. AdditionalUserInfo
credential Um objeto que contém informações sobre a credencial do usuário. AuthCredential

Bloqueando o registro ou login

Para bloquear um registro ou tentativa de login, lance um HttpsError em sua função. Por exemplo:

Node.js

throw new functions.auth.HttpsError('permission-denied');

A tabela a seguir lista os erros que você pode gerar, juntamente com a mensagem de erro padrão:

Nome Código Mensagem
invalid-argument 400 O cliente especificou um argumento inválido.
failed-precondition 400 A solicitação não pode ser executada no estado atual do sistema.
out-of-range 400 O cliente especificou um intervalo inválido.
unauthenticated 401 Token OAuth ausente, inválido ou expirado.
permission-denied 403 O cliente não tem permissão suficiente.
not-found 404 O recurso especificado não foi encontrado.
aborted 409 Conflito de simultaneidade, como um conflito de leitura-modificação-gravação.
already-exists 409 O recurso que um cliente tentou criar já existe.
resource-exhausted 429 Fora da cota de recursos ou atingindo o limite de taxa.
cancelled 499 Pedido cancelado pelo cliente.
data-loss 500 Perda de dados irrecuperável ou corrupção de dados.
unknown 500 Erro de servidor desconhecido.
internal 500 Erro do Servidor Interno.
not-implemented 501 Método de API não implementado pelo servidor.
unavailable 503 Serviço não disponível.
deadline-exceeded 504 Prazo de solicitação excedido.

Você também pode especificar uma mensagem de erro personalizada:

Node.js

throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');

O exemplo a seguir mostra como impedir que usuários que não estejam em um domínio específico se registrem no seu aplicativo:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  // (If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from context.resource, e.g. 'projects/project-id/tenant/tenant-id-1')

  // Only users of a specific domain can sign up.
  if (user.email.indexOf('@acme.com') === -1) {
    throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Independentemente de você usar uma mensagem padrão ou personalizada, o Cloud Functions encapsula o erro e o retorna ao cliente como um erro interno. Por exemplo:

throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);

Seu aplicativo deve detectar o erro e tratá-lo adequadamente. Por exemplo:

JavaScript

// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
firebase.auth().createUserWithEmailAndPassword('johndoe@example.com', 'password')
  .then((result) => {
    result.user.getIdTokenResult()
  })
  .then((idTokenResult) => {
    console.log(idTokenResult.claim.admin);
  })
  .catch((error) => {
    if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
      // Display error.
    } else {
      // Registration succeeds.
    }
  });

Modificando um usuário

Em vez de bloquear um registro ou tentativa de login, você pode permitir que a operação continue, mas modificar o objeto User que é salvo no banco de dados do Firebase Authentication e retornado ao cliente.

Para modificar um usuário, retorne um objeto de seu manipulador de eventos contendo os campos a serem modificados. Você pode modificar os seguintes campos:

  • displayName
  • disabled
  • emailVerified
  • photoURL
  • customClaims
  • sessionClaims (somente beforeSignIn )

Com exceção de sessionClaims , todos os campos modificados são salvos no banco de dados do Firebase Authentication, o que significa que eles são incluídos no token de resposta e persistem entre as sessões do usuário.

O exemplo a seguir mostra como definir um nome de exibição padrão:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  return {
    // If no display name is provided, set it to "Guest".
    displayName: user.displayName || 'Guest';
  };
});

Se você registrar um manipulador de eventos para beforeCreate e beforeSignIn , observe que beforeSignIn é executado após beforeCreate . Os campos de usuário atualizados em beforeCreate são visíveis em beforeSignIn . Se você definir um campo diferente de sessionClaims em ambos os manipuladores de eventos, o valor definido em beforeSignIn substituirá o valor definido em beforeCreate . Somente para sessionClaims , eles são propagados para as declarações de token da sessão atual, mas não são persistidos ou armazenados no banco de dados.

Por exemplo, se algum sessionClaims for definido, beforeSignIn os retornará com quaisquer declarações beforeCreate e eles serão mesclados. Quando eles são mesclados, se uma chave sessionClaims corresponder a uma chave em customClaims , os customClaims correspondentes serão substituídos nas declarações de token pela chave sessionClaims . No entanto, a chave customClaims ainda será mantida no banco de dados para solicitações futuras.

Credenciais e dados OAuth compatíveis

Você pode passar credenciais e dados OAuth para funções de bloqueio de vários provedores de identidade. A tabela a seguir mostra quais credenciais e dados são compatíveis com cada provedor de identidade:

Provedor de identidade Token de ID Token de acesso Data de validade Segredo do token Atualizar token Reivindicações de login
Google Sim Sim Sim Não Sim Não
Facebook Não Sim Sim Não Não Não
Twitter Não Sim Não Sim Não Não
GitHubGenericName Não Sim Não Não Não Não
Microsoft Sim Sim Sim Não Sim Não
LinkedIn Não Sim Sim Não Não Não
Yahoo Sim Sim Sim Não Sim Não
Maçã Sim Sim Sim Não Sim Não
SAML Não Não Não Não Não Sim
OIDC Sim Sim Sim Não Sim Sim

Atualizar tokens

Para usar um token de atualização em uma função de bloqueio, primeiro marque a caixa de seleção na página Funções de bloqueio do console do Firebase.

Os tokens de atualização não serão retornados por nenhum provedor de identidade ao entrar diretamente com uma credencial OAuth, como um token de ID ou token de acesso. Nessa situação, a mesma credencial OAuth do lado do cliente será passada para a função de bloqueio.

As seções a seguir descrevem cada tipo de provedor de identidade e suas credenciais e dados com suporte.

Provedores OIDC genéricos

Quando um usuário entra com um provedor OIDC genérico, as seguintes credenciais serão passadas:

  • Token de ID : fornecido se o fluxo id_token for selecionado.
  • Token de acesso : fornecido se o fluxo de código for selecionado. Observe que o fluxo de código só tem suporte atualmente por meio da API REST.
  • Token de atualização : fornecido se o escopo offline_access for selecionado.

Exemplo:

const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Google

Quando um usuário faz login com o Google, as seguintes credenciais são passadas:

  • token de identificação
  • Token de acesso
  • Atualizar token : fornecido somente se os seguintes parâmetros personalizados forem solicitados:
    • access_type=offline
    • prompt=consent , se o usuário consentiu anteriormente e nenhum novo escopo foi solicitado

Exemplo:

const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
  'access_type': 'offline',
  'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);

Saiba mais sobre os tokens de atualização do Google .

Facebook

Quando um usuário faz login com o Facebook, a seguinte credencial será passada:

  • Token de acesso : é retornado um token de acesso que pode ser trocado por outro token de acesso. Saiba mais sobre os diferentes tipos de tokens de acesso suportados pelo Facebook e como você pode trocá-los por tokens de longa duração .

GitHubGenericName

Quando um usuário entra com o GitHub, a seguinte credencial será passada:

  • Token de acesso : não expira a menos que seja revogado.

Microsoft

Quando um usuário entra com a Microsoft, as seguintes credenciais serão passadas:

  • token de identificação
  • Token de acesso
  • Token de atualização : passado para a função de bloqueio se o escopo offline_access for selecionado.

Exemplo:

const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Yahoo

Quando um usuário faz login com o Yahoo, as seguintes credenciais serão transmitidas sem nenhum parâmetro ou escopo personalizado:

  • token de identificação
  • Token de acesso
  • Atualizar token

LinkedIn

Quando um usuário entra com o LinkedIn, a seguinte credencial será passada:

  • Token de acesso

Maçã

Quando um usuário faz login com a Apple, as seguintes credenciais serão passadas sem nenhum parâmetro ou escopo personalizado:

  • token de identificação
  • Token de acesso
  • Atualizar token

Cenários comuns

Os exemplos a seguir demonstram alguns casos de uso comuns para funções de bloqueio:

Permitindo apenas o registro de um domínio específico

O exemplo a seguir mostra como impedir que usuários que não fazem parte do domínio example.com se registrem no seu aplicativo:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (!user.email || user.email.indexOf('@example.com') === -1) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Impedindo o registro de usuários com e-mails não verificados

O exemplo a seguir mostra como impedir que usuários com e-mails não verificados se registrem no seu aplicativo:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unverified email "${user.email}"`);
  }
});

Exigindo verificação de e-mail no registro

O exemplo a seguir mostra como exigir que um usuário verifique seu e-mail após o registro:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  const locale = context.locale;
  if (user.email && !user.emailVerified) {
    // Send custom email verification on sign-up.
    return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
      return sendCustomVerificationEmail(user.email, link, locale);
    });
  }
});

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
 if (user.email && !user.emailVerified) {
   throw new functions.auth.HttpsError(
     'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
  }
});

Tratar determinados e-mails de provedores de identidade como verificados

O exemplo a seguir mostra como tratar e-mails de usuários de determinados provedores de identidade como verificados:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
    return {
      emailVerified: true,
    };
  }
});

Bloqueando o login de determinados endereços IP

O exemplo a seguir como bloquear o login de determinados intervalos de endereços IP:

Node.js

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
  if (isSuspiciousIpAddress(context.ipAddress)) {
    throw new functions.auth.HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});

Configurando declarações personalizadas e de sessão

O exemplo a seguir mostra como definir declarações personalizadas e de sessão:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'saml.my-provider-id') {
    return {
      // Employee ID does not change so save in persistent claims (stored in
      // Auth DB).
      customClaims: {
        eid: context.credential.claims.employeeid,
      },
      // Copy role and groups to token claims. These will not be persisted.
      sessionClaims: {
        role: context.credential.claims.role,
        groups: context.credential.claims.groups,
      }
    }
  }
});

Rastreamento de endereços IP para monitorar atividades suspeitas

Você pode evitar o roubo de tokens rastreando o endereço IP do qual um usuário faz login e comparando-o com o endereço IP em solicitações subsequentes. Se a solicitação parecer suspeita — por exemplo, os IPs são de diferentes regiões geográficas — você pode pedir ao usuário que faça login novamente.

  1. Use declarações de sessão para rastrear o endereço IP com o qual o usuário faz login:

    Node.js

    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      return {
        sessionClaims: {
          signInIpAddress: context.ipAddress,
        },
      };
    });
    
  2. Quando um usuário tenta acessar recursos que exigem autenticação com o Firebase Authentication, compare o endereço IP na solicitação com o IP usado para fazer login:

    Node.js

    app.post('/getRestrictedData', (req, res) => {
      // Get the ID token passed.
      const idToken = req.body.idToken;
      // Verify the ID token, check if revoked and decode its payload.
      admin.auth().verifyIdToken(idToken, true).then((claims) => {
        // Get request IP address
        const requestIpAddress = req.connection.remoteAddress;
        // Get sign-in IP address.
        const signInIpAddress = claims.signInIpAddress;
        // Check if the request IP address origin is suspicious relative to
        // the session IP addresses. The current request timestamp and the
        // auth_time of the ID token can provide additional signals of abuse,
        // especially if the IP address suddenly changed. If there was a sudden
        // geographical change in a short period of time, then it will give
        // stronger signals of possible abuse.
        if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) {
          // Suspicious IP address change. Require re-authentication.
          // You can also revoke all user sessions by calling:
          // admin.auth().revokeRefreshTokens(claims.sub).
          res.status(401).send({error: 'Unauthorized access. Please login again!'});
        } else {
          // Access is valid. Try to return data.
          getData(claims).then(data => {
            res.end(JSON.stringify(data);
          }, error => {
            res.status(500).send({ error: 'Server error!' })
          });
        }
      });
    });
    

Triagem de fotos do usuário

O exemplo a seguir mostra como limpar as fotos do perfil dos usuários:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.photoURL) {
    return isPhotoAppropriate(user.photoURL)
      .then((status) => {
        if (!status) {
          // Sanitize inappropriate photos by replacing them with guest photos.
          // Users could also be blocked from sign-up, disabled, etc.
          return {
            photoURL: PLACEHOLDER_GUEST_PHOTO_URL,
          };
        }
      });
});

Para saber mais sobre como detectar e limpar imagens, consulte a documentação do Cloud Vision .

Acessando as credenciais OAuth do provedor de identidade de um usuário

O exemplo a seguir demonstra como obter um token de atualização para um usuário que fez login no Google e usá-lo para chamar as APIs do Google Agenda. O token de atualização é armazenado para acesso offline.

Node.js

const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
  keys.web.client_id,
  keys.web.client_secret
);

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'google.com') {
    // Store the refresh token for later offline use.
    // These will only be returned if refresh tokens credentials are included
    // (enabled by Cloud console).
    return saveUserRefreshToken(
        user.uid,
        context.credential.refreshToken,
        'google.com'
      )
      .then(() => {
        // Blocking the function is not required. The function can resolve while
        // this operation continues to run in the background.
        return new Promise((resolve, reject) => {
          // For this operation to succeed, the appropriate OAuth scope should be requested
          // on sign in with Google, client-side. In this case:
          // https://www.googleapis.com/auth/calendar
          // You can check granted_scopes from within:
          // context.additionalUserInfo.profile.granted_scopes (space joined list of scopes).

          // Set access token/refresh token.
          oAuth2Client.setCredentials({
            access_token: context.credential.accessToken,
            refresh_token: context.credential.refreshToken,
          });
          const calendar = google.calendar('v3');
          // Setup Onboarding event on user's calendar.
          const event = {/** ... */};
          calendar.events.insert({
            auth: oauth2client,
            calendarId: 'primary',
            resource: event,
          }, (err, event) => {
            // Do not fail. This is a best effort approach.
            resolve();
          });
      });
    })
  }
});