Uma forma elegante, simples e segura para autenticar usuários e autorizar acessos.
Todo o processo burocrático estará descrito nesse tópico, onde serão mostrados o reaquisitos Lex, e as definições do protocolo.
Precisamos de algumas informações para que possamos criar um registro de aplicação com permissão para se integrar conosco.
Se você está aqui, tem algum contato interno. Utilize esse contato para saber quem deve receber essas informações para processar e seguir adiante com o processo de cadastro e liberação de acesso.
O nome da aplicação, normalmente a mesma do logotipo. Um nome curto, identificável, que será compartilhado com os usuários.
Tamanho máximo: 50 caracteres.
Uma pequena descrição sobre a sua aplicação.
Tamanho máximo: 90 caracteres.
Imagem de impacto que representa a sua aplicação.
Precisamos de dois arquivos distintos.
Importante: Imagem sempre com o fundo transparente!
.SVG
; Dimensão 128x128 pixels
.PNG
; Dimensão 32x32 pixels
.Obs: O nome das imagens deve seguir o seguinte formato produto_xpto.svg
e produto_xpto_32x32.png
para que funcione corretamente no nosso CDN.
A cor principal RGB, formatada como Hexadecimal.
Exemplo: #FF6600
Dentro da lista de perfis de acesso descritas logo abaixo, quais delas fazem parte da permissão de acesso a sua aplicação?
Uma lista de endereços possíveis para onde o usuário pode ser redirecionado após sua autorização. Ao menos uma é requirida.
Exemplos: "https://myapp.com/dashboard" ou "deep_link://myapp.com/android"
Da lista de endereços obtidos no item anterior, repita um único para ser registrado como padrão
.
Sugestão: Que seja um endereço onde você consiga redirecionar de acordo com o estado do usuário. Ou seja, ao entrar nesse endereço deslogado, que eu seja automaticamente redirecionado para a tela de login ou para o botão de login.
(opcional)
Uma lista de endereços possíveis para onde o usuário pode ser redirecionado após se deslogar da sua aplicação.
Importante: Se nenhum endereço for fornecido, após o logout do usuário na sua aplicação, ele será redirecionado para a tela de login da LEX.
Exemplo: "https://myapp.com/post_logout"
Ao encerrar uma sessão do usuário com a aplicação, temos a possibilidade de comunicação entre o provedor de serviço (LEX) e a aplicação (você), via callback. Ele serve para que você possa executar ações para desligamento da sua aplicação, como por exemplo, deletar um cookie que armazena navegação daquela sessão.
Existem duas formas desse callback ser disparado para você, uma é via navegador, e ela faz total sentido quando utiliza o token da LEX ao longo da navegação do usuário na sua aplicação, e a outra é via api, que não depende da interação direta do usuário.
Basta nos informar o URI que devemos cadastrar, e em qual channel (front ou back). Deve ser escolhido somente um dos dois channels.
O callback é feito através de um iframe contido na tela da LEX que quando é carregado você recebe por query string no URI cadastrado o Sid (Session ID) que deve ser usado por você para efetuar o logout, então para que funcione, é relevante que o usuário sempre seja o protagonista do logout e esteja utilizando o mesmo navegador que fez o login.
Sua aplicação recebe o callback no URI cadastrado (e que obrigatoriamente precisa ser um POST) e no form dessa requisição a LEX envia um JWT chamado logout_token. Sua aplicação deve usar esse logout_token para identificar o usuário e invalidar a sessão dele. O aspecto importante do back-channel é que ele não depende de um navegador.
Para permitir o compartilhamento de informações sensíveis de forma segura pela internet, nós utilizamos o protocolo SSH. Entre no link logo abaixo para saber como gerar e compartilhar conosco sua chave pública.
Quando nós recebermos todos os dados requeridos para o seu cadastro, vamos processar e gerar um registro, que chamamos de client
. Esse client
possui informações importantes que irão lhe permitir continuar a integração.
Neste tópico eu vou descrever cada valor que será enviado para você proveniente deste registro, e nos próximos tópicos você entenderá o uso práticos deles.
É um código identificador da aplicação no sistema. Identificamos você através desse código.
É sua senha de acesso. Com ela você será capaz de gerar tokens de acesso, tanto para usuários (authorization_code) quanto para máquinas (client_credentials).
Cálculo matemático que garante a permissão do handshake entre origem e destino válidos.
Qual é o hash exigido para geração de code_verifier
/code_challenge
.
Valor padrão utilizado: S256
.
Utilizando o fluxo authorization_code
!
Para detalhes mais ricos sobre o protocolo e a implementação, recomendamos que você faça a leitura das documentações, que estão no tópico de referências!
Agora faremos uma descrição detalhada do processo com objetivo de facilitar sua implementação e teste. Vamos lá?
Você possui sua aplicação, e precisa de alguma forma identificar para o usuário que ele pode utilizar o login da LEX para se autenticar na sua aplicação. E para que isso dê certo, você precisa de um botão na tela de login e um URI de destino, certo?
Este é o botão “Logar com a LEX”, que você colocará na tela onde os usuários deverão clicar para se autenticar.
E qual é o endereço URI de destino que devo colocar no botão?
Este URI é formado por um endereço fixo, e uma combinação que valores identificados como chave=valor (QueryString
).
Chave | Valor |
---|---|
ISSUER | https://api.lex.education/sso |
AUTH_ROUTE | /connect/authorize |
Chave | Valor |
---|---|
response_type | code |
client_id | Valor recebido pela LEX |
redirect_uri | Qualquer URI previamente cadastrada |
code_challenge_method | S256 |
code_challenge | código gerado - PKCE |
scope | openid profile offline_access |
Aqui vou oferecer dois valores hipotéticos, para demonstrar como o URI ficaria.
Suponha que você tenha cadastrado o endereço de redirecionamento https://localhost:7070/lex
, e que após o seu cadastro na LEX, tenhamos gerado um Identificador de cliente (client_id) com o valor 1234567890abcdfge
para a aplicação, beleza?
Para os seus testes reais, substitua esses dois valores para os que representam sua aplicação. Com os valores hipotéticos não terá sucesso!
Vamos lá montar esse URI:
https://api.lex.education/sso/connect/authorize?response_type=code&client_id=1234567890abcdfge&redirect_uri=https://localhost:7070/lex&code_challenge_method=S256&code_challenge=SzZU4w4&scope=openid profile offline_access
Perceba que esta primeira etapa do processo, obrigatóriamente deverá ser intermediada por um navegador!
Após os seus ajustes, abra o seu navegador de preferência, e cole todo o URI na barra de endereços, e perceba que será redirecionado para a tela de login da LEX.
Assim que o usuário clicar no botão “Logar com a LEX”, com o URI como descrita acima, deverá ser redirecionado para essa tela, como mostrada abaixo.
Assim que preencher os dados corretamente, será redirecionado para o URI que estava no QueryString, com a chave redirect_uri
.
Ao redirecionar, também será acrescentado um QueryString com a chave code
, que será exigida na etapa seguinte!
Com o valor da chave code
em mãos, agora você consegue gerar um token de acesso para o usuário, mas se apresse, pois esse code
é de uso único e valido por apenas 5 minutos!
Chave | Valor |
---|---|
ISSUER | https://api.lex.education/sso |
AUTH_ROUTE | /connect/token |
Chave | Valor |
---|---|
code | Valor recebido no QueryString |
client_id | Valor recebido pela LEX |
client_secret | Valor recebido pela LEX |
redirect_uri | Qualquer URI previamente cadastrada |
code_verifier | código gerado - PKCE |
grant_type | authorization_code |
scope | openid profile offline_access |
Nesta etapa do handshake, você poderá fazer uma requisição para este endpoint com os valores descritos acima, como no exemplo a seguir:
curl --location --request POST 'https://api.lex.education/sso/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=[SEU_ID_DE_CLIENTE]' \
--data-urlencode 'client_secret=[SUA_CHAVE_DE_CLIENTE]' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'scope=offline_access openid profile' \
--data-urlencode 'code=[CODIGO_NO_QUERY_STRING]' \
--data-urlencode 'code_verifier=[GERADO_PELA_APLICACAO]' \
--data-urlencode 'redirect_uri=[ESCOLHIDO_PELA_APLICACAO] '
Você receberá uma resposta como esta:
Com o token em mãos, você já pode utilizar todos os serviços LEX que tiver permissão de acesso.
Temos um endpoint vinculado ao protocolo OIDC, onde informamos para a aplicação os IDs que aquele usuário possui dentro do seu contexto.
curl --location --request GET 'https://api.lex.education/sso/connect/userinfo' \
--header 'Authorization: Bearer [ACCESS_TOKEN_VALUE]’
Você receberá uma resposta como esta:
Você deve ter percebido, no tópico anterior, mais especificamente no ponto Obter o token de usuário, que além das propriedades id_token
e access_token
, também existe uma propriedade chamada refresh_token
, que possui como valor, uma chave normalmente Hexadecimal e menor de tamanho em comparação com o token. Pois bem, essa chave é utilizada para que seja possível gerar um novo token, renovando o tempo de uso na aplicação, sem a necessidade do usuário preencher o seu login e senha novamente.
Chave | Valor |
---|---|
ISSUER | https://api.lex.education/sso |
AUTH_ROUTE | /connect/token |
Chave | Valor |
---|---|
client_id | Valor recebido pela LEX |
client_secret | Valor recebido pela LEX |
refresh_token | Valor recebido no json de resposta ao obter token |
grant_type | refresh_token |
scope | openid profile offline_access |
curl --location --request POST 'https://api.lex.education/sso/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=[SEU_ID_DE_CLIENTE]' \
--data-urlencode 'client_secret=[SUA_CHAVE_DE_CLIENTE]' \
--data-urlencode 'refresh_token=[CHAVE_HEX_OBTIDA_ANTERIORMENTE]' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'scope=offline_access openid profile'
Você receberá uma resposta como esta:
expires_in
lhe retorna o tempo de expiração em segundos, que você precisa tomar cuidado quando tentar utiliar o access_token
em uma api LEX, que pode retornar um erro HTTP 401, pois expirou…Neste momento, o usuário está navegando normalemente em sua aplicação. Em algum momento ele vai desejar encerrar sua sessão, ou simplemente fechar o navegador/aplicativo, e você vai precisar expirar essa sessão.
Nós criamos duas alternativas para que você possa implementar na sua aplicação, e vamos lhe dar um exemplo de como poderia utilizar cada uma delas.
Mas antes de te mostrar as opções, deixa eu compartilhar com você o contrato que deverá ser seguido, análogo a sua escolha. Ou seja, em ambos os casos, você vai sempre enviar os seguintes QueryString
:
Chave | Valor |
---|---|
id_token_hint | O id_token atual do usuário |
post_logout_redirect_uri | URI de redireiconamento após logout |
Apesar da documentação descrever os parâmetros como opcional, nós recomendamos que sempre os preencha.
Haaa, outra informação relevante é que logout tem como parte do processo redirecionar, como nós vimos no QueryString
acima. Então não esqueça de executar o logout com a intermediação de um navegador, utilizar window.location
, ou algo equivalente, para que não tenha problemas no processo de redirecionamento.
Agora vamos lá listar as opções…
Aqui você tem um contexto normal, onde deseja encerrar a sessão do usuário com a sua aplicação, não interferindo na sessão dele com a LEX.
Ao utilizar este endpoint, quando o usuário clicar novamente no botão “Logar com a LEX”, ele será redirecionado para sua aplicação novamente, sem a necessidade de preencher o login e a senha.
Chave | Valor |
---|---|
ISSUER | https://api.lex.education/sso |
ROUTE | /connect/client-endsession |
Exemplo didática, com valores hipotético:
https://api.lex.education/sso/connect/client-endsession?id_token_hint=eyJhbGXVCJ9.eyJzdWIIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflJf36POk6yJV_adQssw5c&post_logout_redirect_uri=https://localhost:7070/public
Aqui você tem um contexto especial, onde precisa encerrar a sessão do usuário com a LEX, além de encerrar com a sua aplicação.
Ao utilizar este endpoint, quando o usuário clicar novamente no botão “Logar com a LEX”, ele será redirecionado para a tela de login da LEX e será necessário preencher suas credenciais (login e senha) novamente.
Chave | Valor |
---|---|
ISSUER | https://api.lex.education/sso |
ROUTE | /connect/full-endsession |
Exemplo didática, com valores hipotético:
https://api.lex.education/sso/connect/full-endsession?id_token_hint=eyJhbIkpXVCJ9.eyDkwIiwibmFtZSI6IkpvjM5MDIyfQ.SflKxwRJSM6POk6yJV_adQssw5c&post_logout_redirect_uri=https://localhost:7070/public
Resposta: client_credentials
!
Neste contexto, não temos a dependência de um usuário para nos comunicarmos, ou seja, são requisições feitas para obter dados de interesse da aplicação, como por exemplo uma sincronização de acessos, ou uma informação importante que se altera ao longo do dia. Fará mais sentido ao se deparar com nossas outras documentações, mas o devido token será mencionado no respectivo contexto, não se preocupe.
Aqui não é o momento mais relevante para falarmos sobre a propriedade
scope
, mas somente para contexto, cada valor oferecido nesta propriedade, lhe dará acesso a um contexto delimitado, que será compartilhado conforme a necessidade.
Chave | Valor |
---|---|
ISSUER | https://api.lex.education/sso |
AUTH_ROUTE | /connect/token |
Chave | Valor |
---|---|
client_id | Valor recebido pela LEX |
client_secret | Valor recebido pela LEX |
grant_type | client_credentials |
scope | optional |
curl --location --request POST 'https://api.lex.education/sso/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=[SEU_ID_DE_CLIENTE]' \
--data-urlencode 'client_secret=[SUA_CHAVE_DE_CLIENTE]' \
--data-urlencode 'grant_type=client_credentials'
Você receberá uma resposta como esta:
Abaixo nós disponibilizamos sugestões de como você pode implementar o protocolo, visando agilizar seu processo de estudo e teste. Em nenhum momento estamos exigindo que a implementação seja exatamente como está descrito, mas sempre tomando cuidado com os campos e processos que são exigidos pelo próprio protocolo, que serão validados.
Se sua linguagem não estiver aqui e esteja precisando de ajuda, nos comunique que faremos o possível para lhe auxiliar.
Será um prazer para nós receber o seu código para nos ajudar a enriquecer essa base de conhecimento que é sempre feita e atualizada com muito carinho para você!
WebApp, Razor e/ou WebApi.
O código na aba ao lado foi construido na versão .NET Core 3.1, e a única diferença entre a implementação compartilhada aqui, com a atual versão LTS .NET 6.0 é a forma de abstração dos objetos.
Para que fique mais claro e também mais simples sua compreensão, vou colocar dois trechos, um de cada versão de códigos, para ilustrar como é simples sua adaptação.
Pode ser necessário algum ajuste extra, mas já fica a dica quando for testar
/// .NET Core 3.1
public void ConfigureServices(IServiceCollection services) {
services.AddOpenIdConnect(config => { config.Authority = "sso_uri" });
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
app.UseAuthentication();
app.UseAuthorization();
}
/// .NET 6.0
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenIdConnect(config => { config.Authority = "sso_uri" });
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
Este pacote é obrigatório para que você consiga configurar sua aplicação para receber e autorizar tokens vindo do sso da LEX.
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
Tem um pacote incrível que vai te auxiliar bastante, não somente nos testes mas no uso do dia-a-dia. O pacote de chama IdentityModel
, e lhe oferece uma abstração da complexidade ao requisitar e/ou recuperar informações pertinentes a authenticação/autorização. Não será dado muito detalhe sobre ele na implementação inicial, pois não é obrigatório o seu uso, mas não poderíamos deixar de alertar sobre a sua existência!
dotnet add package IdentityModel.AspNetCore
Arquivo de Inicialização
[Startup.cs em .NET Core 3.1 ou Program.cs em .NET 6.0]
public class Startup {
///... more code ...
public void ConfigureServices(IServiceCollection services) {
///... more code ...
services.AddAccessTokenManagement();
services
.AddAuthentication(options =>
{
///forma de autenticação local do usuário:
options.DefaultScheme = "Cookies";
///protocolo que define o fluxo de autenticação:
options.DefaultChallengeScheme = "OpenIdConnect";
})
.AddCookie()
.AddOpenIdConnect(config =>
{
config.Authority = "https://homolog-api.lex.education/sso";
config.ClientId = "SEU_CLIENT_ID";
config.ClientSecret = "SEU_CLIENT_SECRET";
config.ResponseType = "code";
config.UsePkce = true;
config.SaveTokens = true;
config.GetClaimsFromUserInfoEndpoint = true;
config.Scope.Clear();
config.Scope.Add("openid");//obrigatório para acesso via OIDC
config.Scope.Add("profile");//para obter dados do usuário
config.Scope.Add("offline_access");//para usar refresh_token flow
config.Scope.Add("lex-account");//permissão de acesso api de contas da LEX
config.Scope.Add("lex-account-user.read");//permissão de leitura aos dados de usuários LEX
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
///... more code ...
app.UseAuthentication();
app.UseAuthorization();
///... more code ...
}
}
Agora basta adicionar a anotação [Authorize]
em uma tela Razor ou em uma rota de endpoint, pois você já está integrado!
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
para a autenticação jwt quando necessário.schemas
diferentes do padrão, precisa identificar corretamente ao invés do que mostrei agora.Se você não tem nenhuma forma interna de autenticar o usuário, ou por algum motivo deseja disponibilizar uma seção para ter acesso exclusivo com o token LEX em sua aplicação, este é o modelo mais simples que pode lhe auxiliar.
Existem tantas outras configurações aqui que são possíveis, para que o acesso seja mais modular, porém vou lhe apresentar somente o conceito, vou deixar os detalhes com você, ok?
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Serve para WebApp ou aplicação web, com tela e também para aplicações de API
Startup.cs (se .NET Core 3.1)
ou
Program.cs (se .NET 6.0)
public class Startup {
///... more code ...
public void ConfigureServices(IServiceCollection services) {
///... more code ...
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://homolog-api.lex.education/sso";
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudiences = new[] { "CLIENT_ID", "YOUR_APP_NAME", "RESOURCE" }
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
///... more code ...
app.UseAuthentication();
///... more code ...
}
}
Agora basta adicionar a anotação [Authorize]
em uma tela Razor ou em uma rota de endpoint, pois você já está integrado!
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
para a autenticação jwt quando necessário.schemas
diferentes do padrão, precisa identificar corretamente ao invés do que mostrei agora.Uma breve explicação onde CORS entra no processo, já que não terá a necessidade de nenhuma implementação do lado da aplicação. Com o(s) domínio(s) dos URIs que você nos passou, iremos cadastrar como domínio válido para receber resposta do servidor de autenticação/autorização para garantir que somente este(s) domínio(s) consigam se comunicar conosco.
//sample.auth.service.ts
import { UserManager, UserManagerSettings } from "oidc-client";
@Injectable({ providedIn: "root", })
export class AuthService {
private manager: UserManager;
private const authConfig: UserManagerSettings = {
authority: "https://homolog-api.lex.education/sso"
client_id: "SEU_CLIENT_ID",
response_type: "code",
response_mode: "query",
redirect_uri: location.origin + "/logged",
silent_redirect_uri: location.origin + "/silent-login-callback",
post_logout_redirect_uri: location.origin + "/public-access",
scope: "openid profile offline_access lex-account lex-account-user.read",
};
constructor() {
this.manager = new UserManager(authConfig);
}
}
//sample.routing.module.ts
import { AllXptoComponents } from "./xptofolder/xpto.component"
const routes: Routes = [
{
path: "*",
component: XptoPageComponent,
canActivate: [AuthGuard],
},...
]
//sample.auth.guard.ts
import { User } from "oidc-client";
import { AuthService } from "./sample.auth.service";
@Injectable()
export class AuthGuard implements CanActivate {
private user: User;
constructor(
private authService: AuthService
) {}
async canActivate() {
this.user = await this.authService.getUser();
if(!this.user || this.user.expired) {
this.user = await this.authService.renewUserToken();
}
return (!this.user) ? false : true;
}
}
Visando uma experiencia incrível, oferecemos para você um ambiente de desenvolvimento para simular tudo aquilo que descrevemos sem se preocupar com as consequências, como por exemplo deixar um aluninho sem acesso a sua aplicação.
Esse ambiente sempre será criado primeiro, com as especificações compartilhadas por você, como descrito nesta documentação, para todos os testes.
https://homolog-sso.lex.education
https://homolog-api.lex.education/sso/.well-known/openid-configuration
https://homolog-api.lex.education/sso/connect/authorize
https://homolog-api.lex.education/sso/connect/token
https://homolog-api.lex.education/sso/connect/userinfo
https://homolog-api.lex.education/sso/connect/client-endsession
https://homolog-api.lex.education/sso/connect/full-endsession
São documentações oficiais falando em detalhes sobre o protocolo e suas aplicações.
Os links foram cadastrados de forma sugestiva, para aprofundamento sobre o assunto de forma natural.