Blazor
- Autenticação e Autorização com Okta
![]() |
Neste artigo vamos implementar a autenticação e autorização com Okta em uma aplicação Blazor Server. |
Antes de pôr a mão na massa vamos apresentar a Okta.
A Okta é uma empresa de tecnologia que oferece soluções de gerenciamento de identidade e acesso (IAM) para organizações. O principal objetivo do Okta é ajudar as empresas a proteger seus sistemas, aplicativos e dados, ao mesmo tempo em que simplifica o processo de autenticação e acesso para os usuários.
Assim vamos usar os recursos oferecidos pela Okta para implementar a autenticação e autorização em uma aplicação Blazor Server.
A autenticação via Okta segue um processo geralmente bastante simplificado, projetado para tornar o acesso a aplicativos e sistemas mais seguro e conveniente para os usuários. Aqui está uma visão geral de como a autenticação via Okta funciona:
Projeto Blazor Server
Vamos iniciar criando um projeto Blazor Server no VS 2022 Community chamado BlazorOkta.
Vamos usar as seguintes configurações :
A seguir vamos alterar no arquivo launchSettings.json do projeto criado as portas usadas na aplicação para 5000 e 5001 no perfil https :
Agora vamos instalar as seguintes dependências no projeto :
Ao final o arquivo de projeto BlazorOkta.csproj terá as seguintes definições:
<Project
Sdk="Microsoft.NET.Sdk.Web"> < PropertyGroup><TargetFramework>net7.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> < ItemGroup><PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.10" /> <PackageReference Include="Okta.Sdk" Version="7.0.0" /> </ItemGroup> </Project> |
Cabe destacar aqui que o OpenID Connect (OIDC) é um protocolo de autenticação que é construído em cima do OAuth 2.0. Ele é projetado para permitir a autenticação segura de usuários em aplicativos da web e móveis.
Quando você está criando uma aplicação Blazor usando a autenticação com o Okta SDK (ou qualquer outro provedor de identidade), o OIDC desempenha um papel fundamental no processo de autenticação.
O OIDC permite que os aplicativos da web autentiquem os usuários usando identidades federadas. Isso significa que um provedor de identidade, como Okta, lida com o processo de autenticação real. O aplicativo Blazor não precisa armazenar senhas dos usuários nem lidar com o processo de autenticação em si.
O OIDC estabelece um fluxo de autenticação padronizado que envolve as seguintes etapas:
1 - Quando um usuário tenta fazer login em seu aplicativo Blazor, ele é redirecionado para a página de login do provedor de identidade (Okta, no nosso caso).
2-O usuário fornece suas credenciais (por exemplo, nome de usuário e senha) no provedor de identidade, que verifica essas credenciais.
3- Se a autenticação for bem-sucedida, o provedor de identidade emite um token de identificação (ID token) e, opcionalmente, um token de acesso (access token).
4. O provedor de identidade redireciona o usuário de volta para o aplicativo Blazor, enviando o token de identificação como parte da resposta.
Configurando o ambiente no Okta
Vamos acessar o site developer.okta.com e, se você não possuir uma conta terá que criar uma conta gratuita.
Lembrando que você terá que usar um email de negócio (emails do yahoo, gmail, etc, não são válidos)
Em seguida, vá para o submenu Applications se clique no botão : Create App Integration
A seguir selecione marque as opções :
E clique no botão Next;
A seguir defina as seguintes configurações :
Name: Blazor (ou
sua escolha)
Grant Type : Authorization Code e Refresh Token
URIs de redirecionamento de login:
https://localhost:5001/authorization-code/callback
URIs de redirecionamento de saída:
https://localhost:5001/signout-callback-oidc
URIs básicos: https://localhost:5001/
Assignments :
Allow everyone in your organization to
access
A seguir salve a configuração definida clicando no botão Save.
Fazendo a integração : Okta <->Blazor
A seguir para fazer a integração da aplicação Okta com a nossa aplicação Blazor vamos definir as seguintes configurações no arquivo appsettings.json do projeto Blazor Server.
É aqui vamos especificar a integração do aplicativo e as credenciais da conta Okta no projeto Blazor. Para isso vamos precisar do seguinte :
Essas informações são obtidas no site da Okta na configuração da aplicação que acabamos de fazer:
Assim vamos criar uma seção Okta e definir o seguinte código no arquivo appsettings.json :
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "Okta": { "Issuer": "https://dev-32XXX405.okta.com/oauth2/default", "ClientId": "0oab<SEU CLIENTID > d7", "ClientSecret": "iCTNxaOUN7NIV6a5BoLo< SEU CLIENT Secret> 2bClFNfV4Wy_M_ILr-sMEhmreY" } } |
Obs: Não esqueça de usar os valores para o SEU usuário nesta configuração
A seguir vamos configurar o Blazor para usar o Okta como um provedor externo de autenticação.
Agora vamos configurar a autenticação e instalar o OpenID Connect em nosso aplicativo. Isso será feito na classe Program.cs.
Primeiro vamos adicionar a Autenticação e inicializar os esquemas padrão (Autenticação, SignIn, SignOut). Isso é seguido pela configuração do OIDC com Okta. O bloco é finalizado adicionando a autenticação de cookie em nossa aplicação. A seguir adicionamos os controladores de autenticação, autorização e mapeamento dos controllers.
O código completo na classe Program vai ficar assim:
using
BlazorOkta.Data; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; var builder = WebApplication.CreateBuilder(args);// Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddSingleton<WeatherForecastService>(); builder.Services.AddAuthentication(authOptions => { authOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; authOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; authOptions.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme; authOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }).AddOpenIdConnect(oidcOptions => { oidcOptions.ClientId = builder.Configuration["Okta:ClientId"]; oidcOptions.ClientSecret = builder.Configuration["Okta:ClientSecret"]; oidcOptions.CallbackPath = "/authorization-code/callback"; oidcOptions.Authority = builder.Configuration["Okta:Issuer"]; oidcOptions.ResponseType = "code"; oidcOptions.SaveTokens = true; oidcOptions.Scope.Add("openid"); oidcOptions.Scope.Add("profile"); oidcOptions.TokenValidationParameters.ValidateIssuer = false; oidcOptions.TokenValidationParameters.NameClaimType = "name"; }).AddCookie(); var app = builder.Build();// Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.MapBlazorHub(); app.Run(); |
Vamos entender o código:
Por fim, o código chama AddCookie para adicionar o esquema de autenticação baseado em cookies. Isso é usado para persistir a autenticação do usuário e gerenciar as sessões.
Criando o LoginController
Vamos agora criar uma pasta Controllers no
projeto e nesta pasta criar um controlador MVC chamado
LoginController que contém 2 métodos, Login e
Logout.
Este controlador vai
configurar as ações corretas para quando quisermos fazer login ou logout. Caso o
usuário não esteja autenticado (ou clique em Login), ele será redirecionado para
a página de login utilizando o endpoint Login deste controlador. Se o usuário
clicar em Sair, enviaremos uma solicitação GET para o método Logout.
Para o endpoint Login, verificamos se o usuário já está autenticado, caso
contrário, retornamos um Challenge ou Desafio (que
os obriga a autenticar).
O método Logout garante que o usuário não seja autenticado antes de desconectá-lo e redirecioná-lo para o URI solicitado. A seguir temos o códeigo de LoginController :
using
Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; namespace BlazorOkta.Controllers;
public class LoginController : Controller
if (User.Identity.IsAuthenticated)
return Challenge();
[HttpGet("Logout")]
if (!User.Identity.IsAuthenticated) await HttpContext.SignOutAsync();
return LocalRedirect(redirectUri); |
Criando o componente LoginDisplay.razor
Vamos criar agora o componente LoginDisplay que é
um componente separado que estamos criando para conter os dois botões, login e
logout, com base no estado de autenticação do usuário. Estamos fazendo uso do
componente <AuthorizeView> que vamos definir
no arquivo App.razor.
O componente <AuthorizeView> habilita dois
outros componentes que usaremos: <Authorized> e
<NotAuthorized> e, eles irão exibir conteúdo desses componentes com
base no estado de autenticação do usuário (esteja ele conectado ou não).
Portanto, se o usuário estiver autenticado, vamos exibir um botão Logout, caso contrário, vamos exibir o botão Login. Vamos criar este componente na pasta Shared com seguinte código:
<AuthorizeView> <Authorized> <a href="#">@context.User.Identity.Name</a> <a href="Logout">Logout</a> </Authorized> <NotAuthorized> <a href=@($"Login?returnUrl={ReturnUrl}")>Log in</a> </NotAuthorized> </AuthorizeView>
@code { [Parameter] public string? ReturnUrl { get; set; } protected override async Task OnInitializedAsync() { ReturnUrl = Navigation.ToBaseRelativePath(Navigation.Uri); } } |
Agora podemos
adicionar o componente LoginDisplay ao componente
MainLayout.razor.
O MainLayout é o layout padrão do nosso aplicativo blazor e vamos agora
adicionar o componente <LoginDisplay />
recém-criado na área superior da barra de navegação (acima da tag âncora About).
O código de MainLayout.razor deve ficar assim:
@inherits
LayoutComponentBase < PageTitle>BlazorOkta</PageTitle>< div class="page"><div class="sidebar"> <NavMenu /> </div> <main> <div class="top-row px-4"> <LoginDisplay /> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> </div> <article class="content px-4"> @Body </article> </main> </div> |
Configurar redirecionamento não autorizado
Vamos agora criar na pasta Shared o componente
RedirectToLogin. que será renderizado em caso de
acesso não autorizado a uma determinada página/recurso.
Se o usuário não
estiver autenticado ou não tiver as permissões corretas para visualizar/editar
aquela página/recurso, este componente será usado para redirecioná-lo para o
login (endpoint de login do LoginController.cs).
Este componente vai pegar a Uri (analisado como um caminho relativo base)
e navegar até a 'página' de Login, e, vai enviar uma solicitação GET para
o endpoint de Login quando inicializado.
A seguir o código do componente RedirectToLogin:
@code { [ Inject] public NavigationManager Navigation { get; set; } protected override async Task OnInitializedAsync(){ var returnUrl = Navigation.ToBaseRelativePath(Navigation.Uri); Navigation.NavigateTo($"Login?returnUrl={returnUrl}", true); } } |
Configurando o App.razor
No componente App.razor vamos habilitar o estado de autenticação em nosso projeto.
Para isso devemos agrupar o componente <Router> em dois componentes:
1- <CascadingValue> isso é usado para expor o AccessToken a todos os nossos componentes.
2- <CascadingAuthenticationState> que habilita o estado de autenticação (para que possamos fazer uso desses componentes <Authorized> / <NotAuthorized>).
Você notará que também estamos usando este último e estamos renderizando o componente <RedirectToLogin /> recém-criado. Dessa forma, se um usuário não estiver logado, o aplicativo o redireciona para a página de login (lá o fluxo de autenticação é iniciado e o usuário será enviado ao Okta para fazer login e depois redirecionado de volta para nós. Essas URLs de redirecionamento nos ajudam faça isso).
Abaixo o código ajustado de App.razor:
<CascadingValue
Name="AccessToken"
Value="AccessToken"> <CascadingAuthenticationState> <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData=@routeData DefaultLayout="@typeof(MainLayout)"> <NotAuthorized> <RedirectToLogin /> </NotAuthorized> <Authorizing> Autorizando... </Authorizing> </AuthorizeRouteView> </Found> <NotFound> <PageTitle>Não encontrado</PageTitle> <LayoutView Layout="@typeof(MainLayout)"> <p role="alert">Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState> </CascadingValue>
@code { } |
Agora é só alegria....
Testando a implementação
Ao executar o
aplicativo e clicar em Login, provavelmente você estará logado sem precisar
inserir seu nome de usuário e senha. Isso ocorre porque você já está logado no
Okta, desde o momento em que criou sua conta e integração com o aplicativo. Se
desejar, saia do Okta para testar isso corretamente.
Agora vamos proteger o componente FetchData
padrão, de forma que somente usuários autenticados possam acessá-lo.
Para isso vamos abrir o componente FecthData e incluir o atributo [Authorize] na parte superior da página:
@page
"/fetchdata" @using BlazorOkta.Data @inject WeatherForecastService ForecastService @attribute [Authorize] < PageTitle>Weather forecast</PageTitle>< h1>Weather forecast</h1>... |
E para ocultar o botão do menu de Shared/NavMenu.razor, basta envolvê-lo em um componente <AuthorizeView>, assim:
... <div class="nav-item px-3"> <AuthorizeView> <NavLink class="nav-link" href="fetchdata"> <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data </NavLink> </AuthorizeView> </div> ... |
Executando o projeto teremos o seguinte resultado:
1- Acessando a aplicação Blazor
2- Clicando no link Log in
Aqui devemos incluir as credenciais do usuário definida no site da Okta.
E assim conseguiremos acessar o endpoint para obter a previsão do tempo:
Pegue o projeto aqui: BlazorOkta.zip
"E (Jesus) os ensinava, dizendo: Não está escrito: A minha casa será chamada,
por todas as nações, casa de oração? Mas vós a tendes feito covil de ladrões."
Marcos 11:17
Referências:
C# - Obtendo a data e a hora por TimeZone
C# - O Struct Guid - Macoratti.net
C# - Checando Null de uma forma mais elegante
DateTime - Macoratti.net
Null o que é isso ? - Macoratti.net
Formatação de data e hora para uma cultura ...
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
C# - Os tipos Nullable (Tipos Anuláveis)