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:

  1. Registro de Usuário: Primeiro, o usuário se registra ou é registrado na plataforma Okta pela organização. Isso envolve a criação de uma conta de usuário no sistema Okta.
  2. Adição de Aplicativos: A organização adiciona os aplicativos que deseja que os usuários acessem por meio do Okta ao seu painel de controle Okta. Isso pode incluir aplicativos web, aplicativos móveis, serviços em nuvem, sistemas internos e muito mais.
  3. Autenticação: Quando um usuário tenta acessar um dos aplicativos registrados, eles são redirecionados para a página de login do Okta. O Okta gerencia a autenticação desse usuário.
  4. Identificação: O usuário fornece suas credenciais de login (como nome de usuário e senha) no Okta. Dependendo das configurações de segurança da organização, podem ser usados métodos adicionais de autenticação, como autenticação multifatorial (MFA), como um código enviado para um dispositivo móvel.
  5. Verificação de Identidade: O Okta verifica as credenciais do usuário em seu banco de dados para garantir que sejam válidas.
  6. Sessão Única (SSO): Se as credenciais forem válidas, o Okta cria uma sessão de autenticação segura e emite um token de autenticação para o usuário. Esse token é usado para autenticar o usuário em aplicativos adicionais sem que eles precisem fazer login novamente (esse é o conceito de Single Sign-On - SSO).
  7. Redirecionamento para Aplicativos: O Okta redireciona o usuário de volta para o aplicativo solicitado, fornecendo o token de autenticação. O aplicativo verifica esse token com o Okta para garantir que seja válido e, se for o caso, concede acesso ao usuário.
  8. Uso do Aplicativo: O usuário pode agora usar o aplicativo normalmente, e o Okta gerencia o acesso contínuo e a segurança da sessão.
  9. Encerramento da Sessão: Quando o usuário sai do aplicativo ou encerra a sessão, o Okta também encerra a sessão do usuário e revoga o token de autenticação.
  10. Monitoramento e Segurança: O Okta oferece recursos avançados de monitoramento de comportamento de usuário e políticas de acesso condicional para garantir a segurança contínua das sessões e dos aplicativos.

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.UseAuthorization();
app.MapControllers();

app.MapBlazorHub();
app.MapFallbackToPage(
"/_Host");
app.Run();

Vamos entender o código:

  • O código começa configurando esquemas de autenticação usando o método AddAuthentication do IServiceCollection do Blazor. Ele define quatro esquemas de autenticação padrão:
  • Adição do OpenID Connect:
    Em seguida, o código usa o método AddOpenIdConnect para adicionar o OpenID Connect (OIDC) como um esquema de autenticação. Isso configura a integração com o provedor de identidade Okta para autenticação baseada em OIDC. Alguns dos principais parâmetros configurados incluem:
  • Adição do Cookie Authentication:

    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
    {
        [HttpGet("Login")]
        public IActionResult Login([FromQuery] string returnUrl)
        {
            var redirectUri = returnUrl is null ? Url.Content("~/") : "/" + returnUrl;

            if (User.Identity.IsAuthenticated)
            {
                return LocalRedirect(redirectUri);
            }

            return Challenge();
        }

        [HttpGet("Logout")]
        public async Task<ActionResult> Logout([FromQuery] string returnUrl)
        {
            var redirectUri = returnUrl is null ? Url.Content("~/") : "/" + returnUrl;

            if (!User.Identity.IsAuthenticated)
            {
                return LocalRedirect(redirectUri);
            }

            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 {
      [
    Inject] public NavigationManager? Navigation { get; set; }
      [
    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 {
       [
    Parameter] public string AccessToken { get; set; }
    }

    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:


    José Carlos Macoratti