Criando seu primeiro Identity Server

Criar o seu primeiro Identity Server com o IdentityServer4 leva poucos minutos quando você usa os templates oficiais do projeto. No artigo anterior, Identity Server: propósito e fundamentos, vimos para que serve um servidor de identidade e quais problemas ele resolve.

Agora vamos colocar a mão na massa. Você vai instalar os templates, gerar o projeto, entender cada arquivo criado e confirmar que o servidor responde corretamente.

Série sobre Identity Server

Este conteúdo faz parte de uma série curta sobre o tema. Por isso, vale seguir a ordem abaixo para aproveitar melhor cada etapa.

O que você precisa saber antes do primeiro Identity Server

O IdentityServer4 é um framework de OpenID Connect e OAuth 2.0 para ASP.NET Core. Em outras palavras, ele transforma uma aplicação web comum em um servidor que emite tokens de acesso e de identidade.

A própria documentação da Microsoft lista os recursos que ele oferece. Entre eles estão a autenticação como serviço, o login único entre vários tipos de aplicação, o controle de acesso para APIs e o papel de gateway de federação.

Na prática, seu primeiro Identity Server vai centralizar o login. Assim, as APIs e os aplicativos deixam de cuidar de senhas e passam apenas a validar tokens assinados pelo servidor.

Se o conceito de token ainda parece abstrato, recomendo ler antes o artigo sobre o que é JWT. Ele explica o formato que o servidor de identidade usa para entregar as informações de acesso.

Situação atual do IdentityServer4

Antes de seguir, existe um ponto importante sobre manutenção. Segundo o repositório oficial, o IdentityServer4 saiu de suporte junto com o .NET Core 3.1, em 13 de dezembro de 2022.

Depois disso, os criadores do projeto continuaram o trabalho em um produto comercial chamado Duende IdentityServer. Além disso, o repositório original foi arquivado e hoje é somente leitura.

Portanto, este tutorial mantém o IdentityServer4 porque ele é a base do artigo e da série. Contudo, para um projeto novo em produção, avalie o Duende IdentityServer, que pode exigir licença paga, ou alternativas como o OpenIddict.

Mesmo assim, montar o primeiro Identity Server com o IdentityServer4 continua sendo uma ótima forma de aprender. Afinal, os conceitos de clientes, escopos, recursos e discovery document são os mesmos nas ferramentas mais recentes.

Pré-requisitos do primeiro Identity Server

Para acompanhar o tutorial, você precisa do SDK do .NET instalado na máquina. Como o IdentityServer4 4.x foi feito para o ASP.NET Core 3.1, o ideal é ter o SDK do .NET Core 3.1 disponível.

Se você ainda não configurou o ambiente, o artigo sobre instalação e primeiros passos com .NET mostra o processo completo. Em seguida, confirme a instalação com o comando dotnet --list-sdks.

Além do SDK, você vai precisar de um terminal e de um editor de código. Qualquer editor funciona, porque todo o trabalho deste artigo acontece pela linha de comando.

Também é útil ter um navegador à mão. Afinal, é por ele que vamos validar o primeiro Identity Server no final do processo.

Templates do IdentityServer4

Embora você possa criar uma aplicação vazia e depois adicionar os pacotes necessários, é muito mais fácil usar um template pronto. Dessa forma, você evita erros de configuração logo no começo.

IMPORTANTE: normalmente mantemos o servidor de identidade em um projeto separado, então não se preocupe tanto com a estrutura do projeto aqui.

Essa separação faz sentido porque o servidor de identidade tem um ciclo de vida próprio. Além disso, ele costuma atender várias aplicações ao mesmo tempo, e não apenas uma.

Instalando os templates

Para instalar os templates oficiais do IdentityServer4, execute o comando abaixo no terminal. Ele baixa o pacote do NuGet e registra os novos modelos no dotnet new.

dotnet new -i IdentityServer4.Templates

Vale uma observação sobre a sintaxe. A partir do SDK do .NET 7, o comando recomendado passou a ser dotnet new install, enquanto a opção -i virou uma forma antiga que ainda funciona.

# Sintaxe equivalente nos SDKs mais novos
dotnet new install IdentityServer4.Templates

Em seguida, você pode listar os modelos instalados com dotnet new --list. Os nomes curtos começam com is4, o que facilita encontrar cada um.

Templates disponíveis

O pacote traz vários templates, cada um pensado para um cenário diferente. A tabela a seguir resume o propósito de cada um, de acordo com o repositório oficial.

Nome curto Para que serve
is4empty Configuração mínima do IdentityServer, sem interface de usuário
is4ui Adiciona a interface de quickstart a um projeto existente
is4inmem Servidor com interface, usuários de teste e clientes de exemplo em memória
is4aspid Servidor integrado ao ASP.NET Core Identity para gerenciar usuários
is4ef Servidor que guarda configuração e estado com Entity Framework
is4admin Servidor com a interface administrativa AdminUI Community Edition

Neste artigo, vamos começar pelo mais básico, o is4empty. Particularmente, uso bastante esse modelo, porque ele entrega só o essencial e deixa claro o que cada parte faz.

Criando seu primeiro Identity Server

Depois de instalar os templates, criar o primeiro Identity Server exige apenas um comando. O parâmetro -n define o nome do projeto e da pasta gerada.

# Cria o servidor de identidade a partir do template vazio
dotnet new is4empty -n MyIdentityServer

Esse processo gera uma aplicação web que roda na porta 5001, com HTTPS, quando você usa o Kestrel. Caso precise mudar essa porta, basta alterar o arquivo Properties\launchSettings.json.

Arquivos gerados no primeiro Identity Server

O template cria uma estrutura pequena e fácil de entender. Na prática, o primeiro Identity Server tem apenas quatro arquivos que importam neste momento.

  • Program.cs: o ponto de entrada da aplicação, que configura o host web.
  • Startup.cs: onde o IdentityServer é registrado e adicionado ao pipeline.
  • Config.cs: onde ficam os recursos, os escopos e os clientes.
  • Properties\launchSettings.json: onde ficam a porta e o perfil de execução.

A documentação chama a abordagem do Config.cs de configuração como código. Ou seja, você descreve clientes e escopos em classes C#, sem banco de dados, o que é ideal para começar.

Entendendo o Startup.cs

O Startup.cs é o coração do primeiro Identity Server. Ele registra o serviço, informa de onde vêm as configurações e liga o middleware.

public class Startup
{
    public IWebHostEnvironment Environment { get; }

    public Startup(IWebHostEnvironment environment)
    {
        Environment = environment;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        // Registra o IdentityServer com as configurações em memória
        var builder = services.AddIdentityServer(options =>
            {
                options.EmitStaticAudienceClaim = true;
            })
            .AddInMemoryIdentityResources(Config.IdentityResources)
            .AddInMemoryApiScopes(Config.ApiScopes)
            .AddInMemoryClients(Config.Clients);

        // Chave de assinatura apenas para desenvolvimento
        builder.AddDeveloperSigningCredential();
    }

    public void Configure(IApplicationBuilder app)
    {
        if (Environment.IsDevelopment())
            app.UseDeveloperExceptionPage();

        // Expõe os endpoints de OpenID Connect e OAuth 2.0
        app.UseIdentityServer();
    }
}

O método AddIdentityServer registra os serviços do framework no container de injeção de dependência. Em seguida, os métodos AddInMemory carregam recursos, escopos e clientes direto das listas do Config.cs.

Já o UseIdentityServer adiciona o IdentityServer ao pipeline de requisições. Com isso, o servidor passa a responder em endpoints padronizados, como o /connect/token.

Entendendo o Config.cs

No template vazio, o Config.cs começa quase sem conteúdo. Ele declara apenas o recurso de identidade obrigatório do OpenID Connect e deixa as outras listas vazias.

public static class Config
{
    // Informações de identidade que podem ser entregues ao cliente
    public static IEnumerable<IdentityResource> IdentityResources =>
        new IdentityResource[]
        {
            new IdentityResources.OpenId()
        };

    // Escopos que representam as APIs protegidas
    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[] { };

    // Aplicações autorizadas a pedir tokens
    public static IEnumerable<Client> Clients =>
        new Client[] { };
}

Os recursos de identidade descrevem dados do usuário, como nome e e-mail. Por outro lado, os escopos de API representam funcionalidades protegidas que um cliente pode acessar com um token.

Os clientes, por fim, são as aplicações que vão pedir tokens ao servidor. Nos próximos artigos, vamos preencher essas listas e conectar aplicativos e APIs ao primeiro Identity Server.

A chave de assinatura de desenvolvimento

Todo token emitido precisa de uma assinatura digital. Assim, quem recebe o token consegue confirmar que ele veio do servidor certo e que ninguém alterou o conteúdo.

O método AddDeveloperSigningCredential resolve isso de forma simples durante o desenvolvimento. Na primeira execução, o IdentityServer4 gera um arquivo chamado tempkey.jwk com a chave usada nas assinaturas.

Esse arquivo não precisa ir para o controle de versão, porque o servidor o recria se ele não existir. No entanto, em produção você deve trocar esse método por uma chave real, carregada com AddSigningCredential.

Executando o primeiro Identity Server

Com a aplicação criada, já podemos executá-la. O servidor sobe e fica pronto para receber as configurações que faremos depois.

cd MyIdentityServer
dotnet watch run

O dotnet watch run reinicia a aplicação sempre que você salva um arquivo. Por isso, ele é muito útil enquanto você ajusta clientes e escopos no Config.cs.

Ao subir, o console mostra os logs do IdentityServer e a URL em que o servidor está escutando. Nesse caso, a URL padrão é https://localhost:5001.

Caso o navegador reclame do certificado, confie no certificado de desenvolvimento do .NET. Para isso, execute dotnet dev-certs https --trust uma única vez na sua máquina.

Validando o primeiro Identity Server

Para verificar se o primeiro Identity Server está rodando adequadamente, acesse a URL abaixo no navegador. Ela devolve o chamado discovery document.

https://localhost:5001/.well-known/openid-configuration

Segundo a documentação do IdentityServer4, esse endpoint retorna metadados do servidor. Entre eles estão o nome do emissor, o material das chaves e os escopos suportados.

O que observar no discovery document

A resposta é um JSON com várias propriedades. Algumas delas merecem atenção logo nesta primeira validação.

  • issuer: o endereço que identifica o servidor e aparece dentro dos tokens.
  • jwks_uri: o endereço onde clientes e APIs buscam a chave pública de validação.
  • authorization_endpoint: o endpoint usado nos fluxos com login interativo.
  • token_endpoint: o endpoint que emite os tokens de acesso.
  • scopes_supported: a lista de escopos que o servidor conhece.
  • grant_types_supported: os fluxos de concessão que o servidor aceita.

Se o JSON aparecer, então o seu primeiro Identity Server está no ar. Além disso, você já pode notar o escopo openid na lista, porque ele veio do Config.cs.

O discovery document também é o que as bibliotecas cliente usam para se configurar sozinhas. Por exemplo, a biblioteca IdentityModel lê esse endpoint para descobrir onde pedir tokens.

Próximos passos depois do primeiro Identity Server

Com o servidor rodando, o próximo passo é cadastrar uma API e um cliente. O quickstart oficial do IdentityServer4 faz isso com o fluxo client credentials, pensado para comunicação entre máquinas.

public static IEnumerable<ApiScope> ApiScopes =>
    new List<ApiScope>
    {
        // Escopo que representa a API protegida
        new ApiScope("api1", "My API")
    };

public static IEnumerable<Client> Clients =>
    new List<Client>
    {
        new Client
        {
            ClientId = "client",

            // Fluxo sem usuário interativo, apenas com o segredo do cliente
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },

            // Escopos que esse cliente pode solicitar
            AllowedScopes = { "api1" }
        }
    };

Nesse exemplo, o cliente se identifica com um ClientId e um segredo. Em seguida, ele pode pedir um token com o escopo api1 e usá-lo para chamar a API protegida.

Do lado da API, a validação do token usa autenticação Bearer com JWT. O artigo sobre autenticação e autorização com JWT e Bearer no ASP.NET 5 mostra como essa parte funciona no ASP.NET.

Além disso, se o seu cenário envolve login de usuários com tela, o template is4inmem é um bom passo seguinte. Ele já traz a interface de quickstart e usuários de teste para você experimentar.

Problemas comuns no primeiro Identity Server

Algumas falhas aparecem com frequência quando você sobe o primeiro Identity Server. Felizmente, quase todas têm uma solução rápida.

Template não encontrado

Se o comando dotnet new is4empty responder que o template não existe, a instalação não foi concluída. Nesse caso, rode novamente a instalação e confira a lista com dotnet new --list.

Também vale verificar se você está sem acesso ao NuGet, por exemplo atrás de um proxy corporativo. Afinal, o pacote de templates vem de lá.

Porta já em uso

Outra situação comum é a porta 5001 já estar ocupada por outra aplicação. Então, o Kestrel não consegue iniciar e mostra um erro de endereço em uso.

Para resolver, encerre a outra aplicação ou altere a porta no launchSettings.json. Lembre apenas de usar a nova porta quando acessar o discovery document.

Erro de certificado

Quando o navegador mostra um aviso de conexão não segura, o certificado de desenvolvimento ainda não é confiável. Como vimos, o comando dotnet dev-certs https --trust resolve esse ponto.

Em sistemas Linux, a confiança no certificado pode exigir passos extras, conforme a distribuição. Por isso, consulte a documentação do ASP.NET Core sobre HTTPS caso o aviso continue.

Boas práticas para o primeiro Identity Server

Mesmo em um projeto de estudo, alguns cuidados evitam surpresas mais tarde. Primeiro, mantenha o primeiro Identity Server em um projeto separado das APIs e dos aplicativos.

Em seguida, nunca use segredos simples como secret fora do ambiente local. Em produção, guarde segredos e chaves em um cofre seguro, como o Azure Key Vault.

Também troque a chave de desenvolvimento por um certificado real antes de publicar. Caso contrário, cada nova instância pode gerar uma chave diferente e invalidar os tokens emitidos.

Por fim, lembre que o IdentityServer4 não recebe mais correções de segurança. Portanto, use este primeiro Identity Server para aprender os conceitos e, para produção, escolha uma solução mantida.

Conclusão

Prontinho, em poucos minutos você tem um servidor de identidade pronto para ser configurado e distribuído. Você instalou os templates, criou o projeto com o is4empty e validou o discovery document.

Além disso, agora você entende o papel de cada arquivo do primeiro Identity Server. O Startup.cs registra o serviço, o Config.cs descreve clientes e escopos, e a chave de desenvolvimento assina os tokens.

Nos próximos artigos, veremos como configurar e conectar nossos aplicativos e APIs a esse servidor. Até lá, explore a documentação do IdentityServer4 no GitHub e experimente os outros templates.

FAQ

Qual template usar para criar o primeiro Identity Server?

O is4empty é o mais indicado para começar, porque traz apenas a configuração mínima. Depois, você pode evoluir para o is4inmem ou o is4aspid.

Em qual porta o IdentityServer4 roda por padrão?

O template is4empty usa HTTPS na porta 5001 quando roda no Kestrel. Você pode alterar isso no arquivo Properties\launchSettings.json.

Como saber se o servidor de identidade está funcionando?

Acesse o endereço /.well-known/openid-configuration no navegador. Se o JSON com os metadados aparecer, o servidor está no ar.

O IdentityServer4 ainda recebe suporte?

Não, o suporte terminou em 13 de dezembro de 2022, junto com o .NET Core 3.1. Os criadores seguiram com o Duende IdentityServer, que é comercial.

Posso versionar o arquivo tempkey.jwk?

Não é necessário, porque o servidor recria o arquivo automaticamente quando ele não existe. Além disso, essa chave serve apenas para desenvolvimento.