ASP.NET Core Blazor Hybrid 인증 및 권한 부여

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

이 문서에서는 Blazor Hybrid 앱의 보안 및 ASP.NET Core Identity 구성과 관리를 위한 ASP.NET Core 지원에 대해 설명합니다.

Blazor Hybrid 앱의 인증은 브라우저 샌드박스가 제공할 수 없는 향상된 보안 보장을 제공하기 때문에 네이티브 플랫폼 라이브러리에 의해 처리됩니다. 네이티브 앱의 인증은 OS 관련 메커니즘을 사용하거나 OIDC(OpenID Connect)와 같은 페더레이션된 프로토콜을 통해 사용합니다. 앱에 대해 선택한 ID 공급자에 대한 지침을 따르고 이 문서의 지침을 사용하여 ID를 Blazor와 추가로 통합합니다.

인증 통합을 수행하려면 Razor 구성 요소 및 서비스에 대한 다음 목표를 달성해야 합니다.

  • Microsoft.AspNetCore.Components.Authorization 패키지에서 추상화(예: AuthorizeView)를 사용합니다.
  • 인증 컨텍스트의 변경 내용에 대응합니다.
  • 권한 있는 API 호출을 수행하기 위해 액세스 토큰과 같이 ID 공급자의 앱에서 프로비전된 자격 증명에 액세스합니다.

인증이 .NET MAUI, WPF 또는 Windows Forms 앱에 추가되고 사용자가 성공적으로 로그인하고 로그아웃할 수 있게 되면 인증된 사용자가 Blazor 구성 요소 및 서비스를 사용할 수 있도록 인증을 Razor와 통합합니다. 다음 단계를 수행합니다.

.NET MAUI 앱은 Xamarin.Essentials: Web Authenticator를 사용합니다. WebAuthenticator 클래스를 사용하면 앱이 앱에 등록된 특정 URL에 대한 콜백을 수신 대기하는 브라우저 기반 인증 흐름을 시작할 수 있습니다.

추가 지침은 다음 리소스를 참조하세요.

Windows Forms 앱은 이 Microsoft ID 플랫폼 사용하여 MICROSOFT Entra(ME-ID) 및 AAD B2C와 통합합니다. 자세한 내용은 MSAL(Microsoft 인증 라이브러리) 개요를 참조하세요.

사용자 변경 업데이트 없이 사용자 지정 AuthenticationStateProvider 만들기

앱이 시작되고 바로 인증된 사용자가 앱 수명 전체에서 동일하게 유지되는 경우 사용자 변경 알림은 필요하지 않으며 앱은 인증된 사용자에 대한 정보만 제공합니다. 이 시나리오에서는 사용자가 앱을 열 때 앱에 로그인하고 사용자가 로그아웃한 후 앱에 로그인 화면을 다시 표시합니다. 다음 ExternalAuthStateProvider는 이 인증 시나리오에 대한 사용자 지정 AuthenticationStateProvider를 구현하는 예제입니다.

참고 항목

다음 사용자 지정 AuthenticationStateProvider는 코드 예제를 모든 Blazor Hybrid 앱에 적용할 수 있도록 하기 위해 네임스페이스를 선언하지 않습니다. 그러나 프로덕션 앱에서 예제를 구현할 때 앱의 네임스페이스를 제공하는 것이 가장 좋습니다.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

다음 단계에서는 아래 방법을 설명합니다.

  • 필수 네임스페이스 추가
  • 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화 추가
  • 서비스 컬렉션 빌드
  • 인증된 사용자의 클레임 주체를 설정하도록 AuthenticatedUser 서비스 확인. 자세한 내용은 ID 공급자 설명서를 참조하세요.
  • 빌드된 호스트 반환

MauiProgram.csMauiProgram.CreateMauiApp 메서드에서 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

빌드된 Microsoft.Maui.Hosting.MauiApp을 반환하는 다음 코드 줄을 제거합니다.

- return builder.Build();

앞의 줄을 다음 코드로 바꿉니다. OpenID/MSAL 코드를 추가하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요.

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

다음 단계에서는 아래 방법을 설명합니다.

  • 필수 네임스페이스 추가
  • 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화 추가
  • 서비스 컬렉션을 빌드하고 빌드된 서비스 컬렉션을 앱의 ResourceDictionary에 리소스로 추가합니다.
  • 인증된 사용자의 클레임 주체를 설정하도록 AuthenticatedUser 서비스 확인. 자세한 내용은 ID 공급자 설명서를 참조하세요.
  • 빌드된 호스트 반환

MainWindow의 생성자(MainWindow.xaml.cs)에서 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

빌드된 서비스 컬렉션을 앱의 ResourceDictionary에 리소스로 추가하는 다음 코드 줄을 제거합니다.

- Resources.Add("services", serviceCollection.BuildServiceProvider());

앞의 줄을 다음 코드로 바꿉니다. OpenID/MSAL 코드를 추가하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요.

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

다음 단계에서는 아래 방법을 설명합니다.

  • 필수 네임스페이스 추가
  • 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화 추가
  • 서비스 컬렉션을 빌드하고 빌드된 서비스 컬렉션을 앱의 서비스 공급자에 추가합니다.
  • 인증된 사용자의 클레임 주체를 설정하도록 AuthenticatedUser 서비스 확인. 자세한 내용은 ID 공급자 설명서를 참조하세요.

Form1의 생성자(Form1.cs)에서 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

빌드된 서비스 컬렉션을 앱의 서비스 공급자로 설정하는 다음 코드 줄을 제거합니다.

- blazorWebView1.Services = services.BuildServiceProvider();

앞의 줄을 다음 코드로 바꿉니다. OpenID/MSAL 코드를 추가하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요.

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

사용자 변경 업데이트로 사용자 지정 AuthenticationStateProvider 만들기

Blazor 앱이 실행되는 동안 사용자를 업데이트하려면 다음 방법 중 하나를 사용하여 AuthenticationStateProvider 구현 내에서 NotifyAuthenticationStateChanged를 호출합니다.

BlazorWebView 외부에서 인증 업데이트를 신호로 표시(옵션 1)

사용자 지정 AuthenticationStateProvider는 글로벌 서비스를 사용하여 인증 업데이트를 신호로 표시할 수 있습니다. AuthenticationStateProvider가 구독할 수 있는 이벤트(이벤트가 NotifyAuthenticationStateChanged를 호출하는 위치)를 서비스에서 제공하는 것이 좋습니다.

참고 항목

다음 사용자 지정 AuthenticationStateProvider는 코드 예제를 모든 Blazor Hybrid 앱에 적용할 수 있도록 하기 위해 네임스페이스를 선언하지 않습니다. 그러나 프로덕션 앱에서 예제를 구현할 때 앱의 네임스페이스를 제공하는 것이 가장 좋습니다.

ExternalAuthStateProvider.cs:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

MauiProgram.csMauiProgram.CreateMauiApp 메서드에서 Microsoft.AspNetCore.Components.Authorization에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;

서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

MainWindow의 생성자(MainWindow.xaml.cs)에서 Microsoft.AspNetCore.Components.Authorization에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;

서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Form1의 생성자(Form1.cs)에서 Microsoft.AspNetCore.Components.Authorization에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;

서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

앱이 사용자를 인증하는 모든 위치에서 ExternalAuthService 서비스를 확인합니다.

var authService = host.Services.GetRequiredService<ExternalAuthService>();

사용자 지정 OpenID/MSAL 코드를 실행하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요. 인증된 사용자(다음 예제에서 authenticatedUser)는 새 ClaimsIdentity를 기반으로 한 새 ClaimsPrincipal입니다.

현재 사용자를 인증된 사용자로 설정합니다.

authService.CurrentUser = authenticatedUser;

이전 방법에 대한 대안은 종속성 주입 컨테이너를 사용하지 않도록 서비스를 통해 사용자의 보안 주체를 설정하는 대신 System.Threading.Thread.CurrentPrincipal에 대한 사용자의 보안 주체를 설정하는 것입니다.

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

대체 방법을 사용하면 권한 부여 서비스(AddAuthorizationCore) 및 CurrentThreadUserAuthenticationStateProvider(.TryAddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>())만 서비스 컬렉션에 추가됩니다.

BlazorWebView 내에서 인증 처리(옵션 2)

사용자 지정 AuthenticationStateProvider에는 로그인 및 로그아웃을 트리거하고 사용자를 업데이트하는 추가 메서드가 포함될 수 있습니다.

참고 항목

다음 사용자 지정 AuthenticationStateProvider는 코드 예제를 모든 Blazor Hybrid 앱에 적용할 수 있도록 하기 위해 네임스페이스를 선언하지 않습니다. 그러나 프로덕션 앱에서 예제를 구현할 때 앱의 네임스페이스를 제공하는 것이 가장 좋습니다.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

앞의 예에서:

  • LogInAsyncCore를 호출하면 로그인 프로세스를 트리거합니다.
  • NotifyAuthenticationStateChanged 호출은 업데이트가 진행 중임을 알리며, 이를 통해 앱은 로그인 또는 로그아웃 프로세스 중에 임시 UI를 제공할 수 있습니다.
  • loginTask 반환은 작업이 완료된 후 로그인을 트리거한 구성 요소가 대기하고 반응할 수 있도록 작업을 반환합니다.
  • 개발자는 ID 공급자의 SDK를 사용하여 사용자가 로그인하도록 LoginWithExternalProviderAsync 메서드를 구현합니다. 자세한 내용은 해당 ID 공급자의 설명서를 참조하세요. 인증된 사용자(authenticatedUser)는 새 ClaimsIdentity를 기반으로 한 새 ClaimsPrincipal입니다.

MauiProgram.csMauiProgram.CreateMauiApp 메서드에서 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

MainWindow의 생성자(MainWindow.xaml.cs)에서 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Form1의 생성자(Form1.cs)에서 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

다음 LoginComponent 구성 요소는 사용자 로그인 방법을 보여 줍니다. 일반적인 앱에서 LoginComponent 구성 요소는 사용자가 앱에 로그인하지 않은 경우에만 부모 구성 요소에 표시됩니다.

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

다음 LogoutComponent 구성 요소는 사용자 로그아웃 방법을 보여 줍니다. 일반적인 앱에서 LogoutComponent 구성 요소는 사용자가 앱에 로그인한 경우에만 부모 구성 요소에 표시됩니다.

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

다른 인증 정보에 액세스

Blazor에서는 웹 API에 대한 HTTP 요청에 사용할 액세스 토큰과 같은 다른 자격 증명을 처리하기 위한 추상화를 정의하지 않습니다. ID 공급자의 지침에 따라 ID 공급자의 SDK가 제공하는 기본 형식으로 사용자의 자격 증명을 관리하는 것이 좋습니다.

ID 공급자 SDK는 일반적으로 디바이스에 저장된 사용자 자격 증명에 토큰 저장소를 사용합니다. SDK의 토큰 저장소 기본 형식이 서비스 컨테이너에 추가되면 앱 내에서 SDK의 기본 형식을 사용합니다.

Blazor 프레임워크는 사용자의 인증 자격 증명을 인식하지 못하며 어떤 방식으로든 자격 증명과 상호 작용하지 않으므로 앱의 코드는 가장 편리하다고 판단되는 모든 접근 방식을 자유롭게 따를 수 있습니다. 그러나 앱에서 인증 코드를 구현할 때 다음 섹션의 일반 보안 지침인 기타 인증 보안 고려 사항을 따릅니다.

기타 인증 보안 고려 사항

인증 프로세스가 Blazor 외부에 있으므로 개발자는 추가 보안 지침에 대해서는 ID 공급자의 지침에 액세스하는 것이 좋습니다.

인증을 구현하는 경우:

  • Web View의 컨텍스트에서 인증을 사용하지 않습니다. 예를 들어 인증 흐름을 수행하는 데 JavaScript OAuth 라이브러리를 사용하지 않습니다. 단일 페이지 앱에서 인증 토큰은 JavaScript에서 숨겨지지 않으며 악의적인 사용자가 쉽게 검색하고 악의적인 목적으로 사용할 수 있습니다. 네이티브 앱은 브라우저 컨텍스트 외부에서만 토큰을 가져올 수 있으므로 이러한 위험이 발생하지 않습니다. 즉, 악의적인 타사 스크립트가 토큰을 도용하고 앱을 손상시킬 수 없습니다.
  • 인증 워크플로를 직접 구현하지 마세요. 대부분의 경우 플랫폼 라이브러리는 하이재킹할 수 있는 사용자 지정 Web View를 사용하는 대신 시스템의 브라우저를 사용하여 인증 워크플로를 안전하게 처리합니다.
  • 플랫폼의 Web View 컨트롤을 사용하여 인증을 수행하지 마세요. 대신 가능하면 시스템의 브라우저를 사용하세요.
  • 문서 컨텍스트(JavaScript)에 토큰을 전달하지 마세요. 경우에 따라 외부 서비스에 대한 권한 있는 호출을 수행하려면 문서 내에 JavaScript 라이브러리가 필요합니다. interop을 통해 JS JavaScript에서 토큰을 사용할 수 있도록 하는 대신 다음을 수행합니다.
    • 생성된 임시 토큰을 라이브러리 및 Web View 내에 제공합니다.
    • 코드에서 나가는 네트워크 요청을 가로챕니다.
    • 임시 토큰을 실제 토큰으로 바꾸고 요청의 대상이 올바른지 확인합니다.

추가 리소스

Blazor Hybrid 앱의 인증은 브라우저 샌드박스가 제공할 수 없는 향상된 보안 보장을 제공하기 때문에 네이티브 플랫폼 라이브러리에 의해 처리됩니다. 네이티브 앱의 인증은 OS 관련 메커니즘을 사용하거나 OIDC(OpenID Connect)와 같은 페더레이션된 프로토콜을 통해 사용합니다. 앱에 대해 선택한 ID 공급자에 대한 지침을 따르고 이 문서의 지침을 사용하여 ID를 Blazor와 추가로 통합합니다.

인증 통합을 수행하려면 Razor 구성 요소 및 서비스에 대한 다음 목표를 달성해야 합니다.

  • Microsoft.AspNetCore.Components.Authorization 패키지에서 추상화(예: AuthorizeView)를 사용합니다.
  • 인증 컨텍스트의 변경 내용에 대응합니다.
  • 권한 있는 API 호출을 수행하기 위해 액세스 토큰과 같이 ID 공급자의 앱에서 프로비전된 자격 증명에 액세스합니다.

인증이 .NET MAUI, WPF 또는 Windows Forms 앱에 추가되고 사용자가 성공적으로 로그인하고 로그아웃할 수 있게 되면 인증된 사용자가 Blazor 구성 요소 및 서비스를 사용할 수 있도록 인증을 Razor와 통합합니다. 다음 단계를 수행합니다.

.NET MAUI 앱은 Xamarin.Essentials: Web Authenticator를 사용합니다. WebAuthenticator 클래스를 사용하면 앱이 앱에 등록된 특정 URL에 대한 콜백을 수신 대기하는 브라우저 기반 인증 흐름을 시작할 수 있습니다.

추가 지침은 다음 리소스를 참조하세요.

Windows Forms 앱은 이 Microsoft ID 플랫폼 사용하여 MICROSOFT Entra(ME-ID) 및 AAD B2C와 통합합니다. 자세한 내용은 MSAL(Microsoft 인증 라이브러리) 개요를 참조하세요.

사용자 변경 업데이트 없이 사용자 지정 AuthenticationStateProvider 만들기

앱이 시작되고 바로 인증된 사용자가 앱 수명 전체에서 동일하게 유지되는 경우 사용자 변경 알림은 필요하지 않으며 앱은 인증된 사용자에 대한 정보만 제공합니다. 이 시나리오에서는 사용자가 앱을 열 때 앱에 로그인하고 사용자가 로그아웃한 후 앱에 로그인 화면을 다시 표시합니다. 다음 ExternalAuthStateProvider는 이 인증 시나리오에 대한 사용자 지정 AuthenticationStateProvider를 구현하는 예제입니다.

참고 항목

다음 사용자 지정 AuthenticationStateProvider는 코드 예제를 모든 Blazor Hybrid 앱에 적용할 수 있도록 하기 위해 네임스페이스를 선언하지 않습니다. 그러나 프로덕션 앱에서 예제를 구현할 때 앱의 네임스페이스를 제공하는 것이 가장 좋습니다.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

다음 단계에서는 아래 방법을 설명합니다.

  • 필수 네임스페이스 추가
  • 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화 추가
  • 서비스 컬렉션 빌드
  • 인증된 사용자의 클레임 주체를 설정하도록 AuthenticatedUser 서비스 확인. 자세한 내용은 ID 공급자 설명서를 참조하세요.
  • 빌드된 호스트 반환

MauiProgram.csMauiProgram.CreateMauiApp 메서드에서 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

빌드된 Microsoft.Maui.Hosting.MauiApp을 반환하는 다음 코드 줄을 제거합니다.

- return builder.Build();

앞의 줄을 다음 코드로 바꿉니다. OpenID/MSAL 코드를 추가하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요.

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

다음 단계에서는 아래 방법을 설명합니다.

  • 필수 네임스페이스 추가
  • 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화 추가
  • 서비스 컬렉션을 빌드하고 빌드된 서비스 컬렉션을 앱의 ResourceDictionary에 리소스로 추가합니다.
  • 인증된 사용자의 클레임 주체를 설정하도록 AuthenticatedUser 서비스 확인. 자세한 내용은 ID 공급자 설명서를 참조하세요.
  • 빌드된 호스트 반환

MainWindow의 생성자(MainWindow.xaml.cs)에서 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

빌드된 서비스 컬렉션을 앱의 ResourceDictionary에 리소스로 추가하는 다음 코드 줄을 제거합니다.

- Resources.Add("services", serviceCollection.BuildServiceProvider());

앞의 줄을 다음 코드로 바꿉니다. OpenID/MSAL 코드를 추가하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요.

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

다음 단계에서는 아래 방법을 설명합니다.

  • 필수 네임스페이스 추가
  • 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화 추가
  • 서비스 컬렉션을 빌드하고 빌드된 서비스 컬렉션을 앱의 서비스 공급자에 추가합니다.
  • 인증된 사용자의 클레임 주체를 설정하도록 AuthenticatedUser 서비스 확인. 자세한 내용은 ID 공급자 설명서를 참조하세요.

Form1의 생성자(Form1.cs)에서 Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

빌드된 서비스 컬렉션을 앱의 서비스 공급자로 설정하는 다음 코드 줄을 제거합니다.

- blazorWebView1.Services = services.BuildServiceProvider();

앞의 줄을 다음 코드로 바꿉니다. OpenID/MSAL 코드를 추가하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요.

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

사용자 변경 업데이트로 사용자 지정 AuthenticationStateProvider 만들기

Blazor 앱이 실행되는 동안 사용자를 업데이트하려면 다음 방법 중 하나를 사용하여 AuthenticationStateProvider 구현 내에서 NotifyAuthenticationStateChanged를 호출합니다.

BlazorWebView 외부에서 인증 업데이트를 신호로 표시(옵션 1)

사용자 지정 AuthenticationStateProvider는 글로벌 서비스를 사용하여 인증 업데이트를 신호로 표시할 수 있습니다. AuthenticationStateProvider가 구독할 수 있는 이벤트(이벤트가 NotifyAuthenticationStateChanged를 호출하는 위치)를 서비스에서 제공하는 것이 좋습니다.

참고 항목

다음 사용자 지정 AuthenticationStateProvider는 코드 예제를 모든 Blazor Hybrid 앱에 적용할 수 있도록 하기 위해 네임스페이스를 선언하지 않습니다. 그러나 프로덕션 앱에서 예제를 구현할 때 앱의 네임스페이스를 제공하는 것이 가장 좋습니다.

ExternalAuthStateProvider.cs:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

MauiProgram.csMauiProgram.CreateMauiApp 메서드에서 Microsoft.AspNetCore.Components.Authorization에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;

서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

MainWindow의 생성자(MainWindow.xaml.cs)에서 Microsoft.AspNetCore.Components.Authorization에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;

서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Form1의 생성자(Form1.cs)에서 Microsoft.AspNetCore.Components.Authorization에 대한 네임스페이스를 추가합니다.

using Microsoft.AspNetCore.Components.Authorization;

서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

앱이 사용자를 인증하는 모든 위치에서 ExternalAuthService 서비스를 확인합니다.

var authService = host.Services.GetRequiredService<ExternalAuthService>();

사용자 지정 OpenID/MSAL 코드를 실행하여 사용자를 인증합니다. 자세한 내용은 ID 공급자 설명서를 참조하세요. 인증된 사용자(다음 예제에서 authenticatedUser)는 새 ClaimsIdentity를 기반으로 한 새 ClaimsPrincipal입니다.

현재 사용자를 인증된 사용자로 설정합니다.

authService.CurrentUser = authenticatedUser;

이전 방법에 대한 대안은 종속성 주입 컨테이너를 사용하지 않도록 서비스를 통해 사용자의 보안 주체를 설정하는 대신 System.Threading.Thread.CurrentPrincipal에 대한 사용자의 보안 주체를 설정하는 것입니다.

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

대체 방법을 사용하면 권한 부여 서비스(AddAuthorizationCore) 및 CurrentThreadUserAuthenticationStateProvider(.AddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>())만 서비스 컬렉션에 추가됩니다.

BlazorWebView 내에서 인증 처리(옵션 2)

사용자 지정 AuthenticationStateProvider에는 로그인 및 로그아웃을 트리거하고 사용자를 업데이트하는 추가 메서드가 포함될 수 있습니다.

참고 항목

다음 사용자 지정 AuthenticationStateProvider는 코드 예제를 모든 Blazor Hybrid 앱에 적용할 수 있도록 하기 위해 네임스페이스를 선언하지 않습니다. 그러나 프로덕션 앱에서 예제를 구현할 때 앱의 네임스페이스를 제공하는 것이 가장 좋습니다.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

앞의 예에서:

  • LogInAsyncCore를 호출하면 로그인 프로세스를 트리거합니다.
  • NotifyAuthenticationStateChanged 호출은 업데이트가 진행 중임을 알리며, 이를 통해 앱은 로그인 또는 로그아웃 프로세스 중에 임시 UI를 제공할 수 있습니다.
  • loginTask 반환은 작업이 완료된 후 로그인을 트리거한 구성 요소가 대기하고 반응할 수 있도록 작업을 반환합니다.
  • 개발자는 ID 공급자의 SDK를 사용하여 사용자가 로그인하도록 LoginWithExternalProviderAsync 메서드를 구현합니다. 자세한 내용은 해당 ID 공급자의 설명서를 참조하세요. 인증된 사용자(authenticatedUser)는 새 ClaimsIdentity를 기반으로 한 새 ClaimsPrincipal입니다.

MauiProgram.csMauiProgram.CreateMauiApp 메서드에서 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

MainWindow의 생성자(MainWindow.xaml.cs)에서 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Form1의 생성자(Form1.cs)에서 서비스 컬렉션에 권한 부여 서비스 및 Blazor 추상화를 추가합니다.

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

다음 LoginComponent 구성 요소는 사용자 로그인 방법을 보여 줍니다. 일반적인 앱에서 LoginComponent 구성 요소는 사용자가 앱에 로그인하지 않은 경우에만 부모 구성 요소에 표시됩니다.

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

다음 LogoutComponent 구성 요소는 사용자 로그아웃 방법을 보여 줍니다. 일반적인 앱에서 LogoutComponent 구성 요소는 사용자가 앱에 로그인한 경우에만 부모 구성 요소에 표시됩니다.

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

다른 인증 정보에 액세스

Blazor에서는 웹 API에 대한 HTTP 요청에 사용할 액세스 토큰과 같은 다른 자격 증명을 처리하기 위한 추상화를 정의하지 않습니다. ID 공급자의 지침에 따라 ID 공급자의 SDK가 제공하는 기본 형식으로 사용자의 자격 증명을 관리하는 것이 좋습니다.

ID 공급자 SDK는 일반적으로 디바이스에 저장된 사용자 자격 증명에 토큰 저장소를 사용합니다. SDK의 토큰 저장소 기본 형식이 서비스 컨테이너에 추가되면 앱 내에서 SDK의 기본 형식을 사용합니다.

Blazor 프레임워크는 사용자의 인증 자격 증명을 인식하지 못하며 어떤 방식으로든 자격 증명과 상호 작용하지 않으므로 앱의 코드는 가장 편리하다고 판단되는 모든 접근 방식을 자유롭게 따를 수 있습니다. 그러나 앱에서 인증 코드를 구현할 때 다음 섹션의 일반 보안 지침인 기타 인증 보안 고려 사항을 따릅니다.

기타 인증 보안 고려 사항

인증 프로세스가 Blazor 외부에 있으므로 개발자는 추가 보안 지침에 대해서는 ID 공급자의 지침에 액세스하는 것이 좋습니다.

인증을 구현하는 경우:

  • Web View의 컨텍스트에서 인증을 사용하지 않습니다. 예를 들어 인증 흐름을 수행하는 데 JavaScript OAuth 라이브러리를 사용하지 않습니다. 단일 페이지 앱에서 인증 토큰은 JavaScript에서 숨겨지지 않으며 악의적인 사용자가 쉽게 검색하고 악의적인 목적으로 사용할 수 있습니다. 네이티브 앱은 브라우저 컨텍스트 외부에서만 토큰을 가져올 수 있으므로 이러한 위험이 발생하지 않습니다. 즉, 악의적인 타사 스크립트가 토큰을 도용하고 앱을 손상시킬 수 없습니다.
  • 인증 워크플로를 직접 구현하지 마세요. 대부분의 경우 플랫폼 라이브러리는 하이재킹할 수 있는 사용자 지정 Web View를 사용하는 대신 시스템의 브라우저를 사용하여 인증 워크플로를 안전하게 처리합니다.
  • 플랫폼의 Web View 컨트롤을 사용하여 인증을 수행하지 마세요. 대신 가능하면 시스템의 브라우저를 사용하세요.
  • 문서 컨텍스트(JavaScript)에 토큰을 전달하지 마세요. 경우에 따라 외부 서비스에 대한 권한 있는 호출을 수행하려면 문서 내에 JavaScript 라이브러리가 필요합니다. interop을 통해 JS JavaScript에서 토큰을 사용할 수 있도록 하는 대신 다음을 수행합니다.
    • 생성된 임시 토큰을 라이브러리 및 Web View 내에 제공합니다.
    • 코드에서 나가는 네트워크 요청을 가로챕니다.
    • 임시 토큰을 실제 토큰으로 바꾸고 요청의 대상이 올바른지 확인합니다.

추가 리소스