ASP.NET Core 3.1 - Autenticando sua API com Google via Firebase
O Firebase Authentication permite que sua aplicação faça login com o Google e que uma API ASP.NET Core 3.1 valide esse acesso com poucas linhas de código. Neste artigo, vamos configurar o projeto no Firebase, autenticar o usuário no frontend e proteger a API com tokens JWT.
Praticamente toda aplicação precisa de autenticação e, hoje em dia, entrar com uma rede social traz muita praticidade para o usuário.
Mas como fica a API nesse cenário? Como validamos o token e como recuperamos os dados do usuário?
Por que não usar o ASP.NET Identity
Quem me conhece sabe que não uso o ASP.NET Identity para quase nada. Acho ele bem útil em aplicações com Razor, porém, no cenário de APIs, criar autenticação e autorização é bem simples.
Por incrível que pareça, fazer autenticação com Google, Facebook, GitHub ou mesmo com um Identity Server próprio também é simples. Afinal, tudo de que precisamos é o padrão OAuth combinado com tokens JWT.
Nesse modelo, a API não guarda senhas nem gerencia contas. Em vez disso, ela confia em um provedor de identidade e apenas verifica se o token recebido foi emitido por ele.
Por que usar o Firebase Authentication
O primeiro ponto é a criação dos usuários e o setup das contas. O Firebase Authentication já traz tudo isso pronto, com uma facilidade incrível, então ele é uma ótima saída para quem não quer perder tempo com uma configuração complexa.
Além disso, o Firebase fornece SDKs para JavaScript, Node, Java, .NET e várias outras linguagens. Ou seja, ele já entrega tudo pronto também para o nosso frontend.
Outro ponto positivo é que o mesmo projeto suporta vários provedores ao mesmo tempo. Portanto, adicionar Facebook, GitHub ou e-mail e senha depois exige apenas habilitá-los no console.
Criando o projeto no console do Firebase
O primeiro passo é acessar o console do Firebase, fazer login e criar um novo projeto. Em seguida, vá até Authentication, abra a aba de métodos de login e habilite o Google.
https://console.firebase.google.com/project/YOUR_PROJECT/authentication/providers
Por fim, abra as configurações do projeto e adicione uma nova aplicação. Para esta demonstração, vamos registrar uma aplicação web.
https://console.firebase.google.com/project/YOUR_PROJECT/settings/general/
Ao registrar a aplicação, o console exibe um trecho de código com as configurações do projeto. Guarde esse conteúdo, pois vamos utilizá-lo em breve.
<!-- O SDK principal do Firebase é sempre obrigatório e deve vir primeiro -->
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-app.js"></script>
<script>
// Configuração da aplicação web no Firebase
var firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "your-project.firebaseapp.com",
databaseURL: "https://your-project.firebaseio.com",
projectId: "your-project",
storageBucket: "your-project.appspot.com",
messagingSenderId: "000000000000",
appId: "1:000000000000:web:0000000000000000"
};
// Inicializa o Firebase
firebase.initializeApp(firebaseConfig);
</script>
O campo mais importante para a API é o projectId. Ele identifica o projeto e será usado mais adiante na validação do token.
Autenticando no frontend com o Firebase Authentication
O primeiro passo é autenticar o usuário no frontend, seja ele HTML puro, Angular, React, Vue ou qualquer outro. Esse processo é o mesmo que realizamos em qualquer aplicação SPA.
Nele, o frontend vai até o servidor de autenticação, que em alguns casos pode ser a nossa própria API, e recebe um token no formato JWT. Com o token em mãos, enviamos esse valor no cabeçalho de cada requisição para a API.
Nunca confie nos dados do frontend
Antes de seguir, um alerta importante: nunca, em hipótese alguma, confie nos dados vindos do frontend. Talvez você esteja se perguntando por que não enviar apenas o e-mail do usuário, já que recebemos nome, e-mail e foto do Google.
O problema é que qualquer pessoa poderia se passar por você nesse caso. Basta abrir o Postman, por exemplo, e fazer uma requisição para a API informando um e-mail diferente.
Por isso, o processo será sempre enviar o token, que é assinado pelos servidores do Google. Em seguida, a API valida essa assinatura e só então lê os dados do usuário.
É assim que todo servidor de identidade funciona: ele emite e valida os tokens. Isso vale para Google, Facebook, GitHub, Azure AD, Identity Server e até para um servidor de identidade próprio.
Incluindo o SDK do Firebase
No meu caso, criei uma nova API com ASP.NET Core usando o comando dotnet new webapi -o Todo. Dentro dessa API, criei uma pasta Client com um arquivo index.html.
O SDK do Firebase é modularizado, então precisamos incluir dois arquivos: um base e outro para autenticação.
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-auth.js"></script>
Repare que os dois arquivos usam a mesma versão do SDK. Misturar versões diferentes entre o módulo base e o de autenticação pode gerar erros difíceis de diagnosticar.
O próximo passo é inicializar o Firebase com as configurações que recebemos ao criar o app no console.
var firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "your-project.firebaseapp.com",
databaseURL: "https://your-project.firebaseio.com",
projectId: "your-project",
storageBucket: "your-project.appspot.com",
messagingSenderId: "000000000000",
appId: "1:000000000000:web:0000000000000000"
};
// Inicializa o Firebase
firebase.initializeApp(firebaseConfig);
A apiKey do Firebase identifica o projeto e não funciona como uma senha. Mesmo assim, configure os domínios autorizados no console, para que apenas os seus sites consigam iniciar o login.
Servindo os arquivos localmente
Pronto, o app já está registrado e inicializado. Neste momento, você já pode testar a página, e recomendo usar o HTTP Server para isso.
Abra um terminal, navegue até a pasta Client e execute os comandos abaixo.
npm install -g http-server
http-server -c1
O primeiro comando instala o HTTP Server na sua máquina, e o segundo sobe um servidor de arquivos estáticos na pasta atual. O parâmetro -c1 define o cache em apenas um segundo, ou seja, não queremos cachear nada.
Feito isso, o app fica disponível em http://localhost:8080. Precisamos desse servidor porque o Firebase não aceita login a partir de arquivos abertos direto do disco, apenas a partir de URLs.
Configurando o provedor do Google
Com o Firebase configurado, é hora de preparar a autenticação. O Firebase Authentication oferece diversos provedores, entre eles o do Google.
Além do provedor, podemos informar escopos, que definem quais informações do usuário queremos acessar. Sem nenhum escopo adicional, o login já retorna os dados básicos do perfil, como nome, e-mail e foto.
// Inicializa o Firebase
firebase.initializeApp(firebaseConfig);
var provider = new firebase.auth.GoogleAuthProvider();
provider.addScope("https://www.googleapis.com/auth/contacts.readonly");
Essa configuração é a mesma para qualquer provedor, mudando apenas a classe e a URL do escopo. Agora, basta executar o login.
function login() {
firebase.auth().signInWithPopup(provider).then(function (result) {
// ESTE NÃO É O JWT, É O TOKEN PARA OBTER INFORMAÇÕES DO GOOGLE
var googleAccessToken = result.credential.accessToken;
var user = result.user;
firebase.auth()
.currentUser
.getIdToken()
.then(function (idToken) {
// ESTE É O TOKEN JWT QUE VAMOS ENVIAR PARA A API
console.log(idToken);
})
.catch(function (error) {
console.log(error);
});
}).catch(function (error) {
// Algo deu errado no login
console.log(error);
});
}
Os dois tokens do login com Firebase
Deixei alguns comentários no código para destacar que existem dois tokens durante o login. O primeiro é o access token do Google, usado para chamar as APIs do Google com os escopos solicitados.
O segundo é o ID token do Firebase, obtido com getIdToken, que é o JWT que de fato queremos enviar para a nossa API. Ele é assinado pelo Firebase e expira em uma hora, mas o SDK renova esse valor automaticamente enquanto o usuário estiver logado.
Nesse exemplo, usamos o signInWithPopup, que abre aquela famosa janela do Google para o usuário se autenticar. Em dispositivos móveis, porém, o signInWithRedirect costuma oferecer uma experiência melhor.
Por fim, deixei o token sendo exibido no console do navegador, para facilitar a cópia e o teste da API.
Página completa do cliente
Falta apenas o botão de login, que ficou assim.
<button onclick="login()">SIGN IN WITH GOOGLE</button>
Pronto, temos o frontend se autenticando com o Google e escrevendo o token no console após o login. O resultado final do arquivo Client/index.html é este aqui.
<html>
<head>
<title>Fire</title>
</head>
<body>
<button onclick="login()">SIGN IN WITH GOOGLE</button>
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-auth.js"></script>
<script>
var firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "your-project.firebaseapp.com",
databaseURL: "https://your-project.firebaseio.com",
projectId: "your-project",
storageBucket: "your-project.appspot.com",
messagingSenderId: "000000000000",
appId: "1:000000000000:web:0000000000000000"
};
// Inicializa o Firebase
firebase.initializeApp(firebaseConfig);
var provider = new firebase.auth.GoogleAuthProvider();
provider.addScope("https://www.googleapis.com/auth/contacts.readonly");
function login() {
firebase.auth().signInWithPopup(provider).then(function (result) {
// ESTE NÃO É O JWT, É O TOKEN PARA OBTER INFORMAÇÕES DO GOOGLE
var googleAccessToken = result.credential.accessToken;
var user = result.user;
firebase.auth()
.currentUser
.getIdToken()
.then(function (idToken) {
// ESTE É O TOKEN JWT QUE VAMOS ENVIAR PARA A API
console.log(idToken);
})
.catch(function (error) {
console.log(error);
});
}).catch(function (error) {
// Algo deu errado no login
console.log(error);
});
}
</script>
</body>
</html>
Validando o Firebase Authentication na API
Agora vem a parte mais legal: autenticar no backend. A API precisa receber o token no cabeçalho da requisição e validá-lo com as chaves públicas do Google para liberar o acesso.
O processo é o mesmo que fazemos em uma autenticação JWT convencional. A única diferença é que quem emite o token é o Firebase, e não a nossa própria API.
Instalando o pacote JwtBearer
Vamos começar adicionando o pacote de autenticação JWT à API. Como o projeto usa o ASP.NET Core 3.1, escolha a versão 3.1 do pacote.
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 3.1.0
Feito isso, precisamos garantir que a API exige autenticação e autorização. Para isso, vá até o método Configure do Startup.cs e verifique se estas duas linhas existem.
app.UseAuthentication();
app.UseAuthorization();
A ordem importa: a autenticação identifica o usuário, e só depois a autorização decide se ele pode acessar o recurso. Além disso, as duas chamadas precisam ficar entre o UseRouting e o UseEndpoints.
Se quiser entender melhor como essa ordem funciona no pipeline, veja o artigo sobre middleware no ASP.NET e como criar o seu.
O que a API verifica no token
A documentação de verificação de ID tokens do Firebase lista as regras que um token precisa cumprir. A configuração acima cobre todas elas com os validadores padrão do ASP.NET Core.
- Assinatura: o algoritmo deve ser RS256, com uma chave pública publicada pelo Google.
- exp: a data de expiração precisa estar no futuro.
- iat: a data de emissão precisa estar no passado.
- aud: precisa ser igual ao ID do projeto no Firebase.
- iss: precisa ser
https://securetoken.google.com/seguido do ID do projeto. - sub: precisa ser uma string não vazia, igual ao identificador do usuário.
Em outras palavras, um token emitido para outro projeto, vencido ou adulterado é rejeitado automaticamente. Dessa forma, a API não precisa escrever nenhuma linha de validação manual.
Testando a autenticação com Firebase
Com tudo configurado, abra qualquer método do controller padrão e adicione o atributo [Authorize]. Em seguida, tente chamar esse método pelo Postman, por exemplo, e você vai receber o erro 401 Unauthorized.
Agora copie o token gerado no console do navegador e faça uma nova requisição para a mesma URL. Desta vez, informe o cabeçalho abaixo.
Authorization: Bearer YOUR_TOKEN
Como mágica, a API validou o token com as chaves do Google e liberou o acesso ao método. Incrível, não é?
Com pouquíssimas linhas de código, integramos a API ao Google e ainda usamos os atributos padrão do ASP.NET Core. E o melhor: sem ASP.NET Identity.
Lendo os dados do usuário
Para obter os dados do usuário, basta ler as claims disponíveis em User.Claims. Ali fica uma lista com tudo o que o token carrega, como nome, foto e e-mail.
O código final do controller ficou assim.
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Todo.Controllers
{
[ApiController]
[Route("")]
public class HomeController : ControllerBase
{
[HttpGet]
[Authorize]
public dynamic Get()
{
return new
{
name = User.Claims.FirstOrDefault(x => x.Type == "name")?.Value,
// email = User.Claims.FirstOrDefault(x => x.Type == "email")?.Value,
picture = User.Claims.FirstOrDefault(x => x.Type == "picture")?.Value
};
}
}
}
Note que o operador ?. evita uma exceção caso alguma claim não exista no token. Assim, o endpoint continua respondendo mesmo para contas sem foto, por exemplo.
Atenção ao mapeamento de claims
Por padrão, o handler JWT do ASP.NET Core 3.1 renomeia algumas claims conhecidas para os tipos longos do .NET. É o caso de email e sub, que passam a usar os tipos de ClaimTypes.
Por isso, buscar a claim email pelo nome curto não encontra o valor. Nesse caso, use ClaimTypes.Email ou limpe o mapeamento com JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear() antes de configurar a autenticação.
O identificador único do usuário no Firebase fica na claim user_id e também em sub. Portanto, use esse valor como chave para relacionar o usuário com os dados do seu banco.
Boas práticas com Firebase Authentication
Guarde o ID do projeto nas configurações da aplicação, em vez de deixá-lo fixo no código. Dessa forma, fica fácil usar projetos diferentes para desenvolvimento e produção.
No frontend, chame getIdToken antes de cada requisição, em vez de guardar o token manualmente. Assim, o SDK entrega sempre um token válido e cuida da renovação para você.
Além disso, sirva a API apenas por HTTPS, porque o token dá acesso total à conta do usuário enquanto for válido. Para conhecer outras medidas de proteção, confira o artigo sobre segurança em APIs ASP.NET Core.
Por fim, lembre-se de que autenticação não é autorização. O token diz quem é o usuário, mas as regras de quem pode fazer o quê continuam sendo responsabilidade da sua API.
Conclusão
Com o Firebase Authentication, o login com Google fica pronto em poucos minutos no frontend. Do lado da API, o ASP.NET Core 3.1 valida o token com apenas a configuração do JwtBearer.
O resultado é uma autenticação segura, sem senhas guardadas no seu banco e sem depender do ASP.NET Identity. Além disso, a mesma estrutura funciona para outros provedores que o Firebase suporta.
O código-fonte completo deste exemplo está no meu GitHub, no repositório aspnetcore-firebase-google-login.
FAQ
Preciso do ASP.NET Identity para usar login com Google?
Não, a API só precisa validar o JWT emitido pelo Firebase. O pacote JwtBearer faz esse trabalho com a configuração de Authority, Issuer e Audience.
Qual token devo enviar para a API?
Envie o ID token obtido com getIdToken. O access token do Google serve apenas para chamar as APIs do próprio Google.
Por quanto tempo o ID token do Firebase é válido?
Ele expira em uma hora, mas o SDK do Firebase renova o valor automaticamente enquanto o usuário continua logado.
Por que a claim de e-mail não aparece pelo nome curto?
O handler JWT do ASP.NET Core 3.1 mapeia algumas claims para os tipos de ClaimTypes. Use ClaimTypes.Email ou limpe o mapeamento padrão.
Posso usar outros provedores além do Google?
Sim, basta habilitar o provedor no console e trocar a classe no frontend. A configuração da API continua exatamente a mesma.