Azure Active Directory B2C를 사용하여 호스트된 ASP.NET Core Blazor WebAssembly 앱 보호

이 문서에서는 인증을 위해 AAD(Azure Active Directory) B2C를 사용하는 호스트된 Blazor WebAssembly 솔루션을 만드는 방법을 설명합니다.

이 문서를 읽은 후 추가 보안 시나리오에 대한 자세한 내용은 ASP.NET Core Blazor WebAssembly 추가 보안 시나리오를 참조하세요.

연습

연습의 하위 섹션에서는 다음 방법을 설명합니다.

  • Azure에서 테넌트 만들기
  • Azure에서 서버 API 앱 등록
  • Azure에서 클라이언트 앱 등록
  • Blazor 앱 만들기
  • 기본 액세스 토큰 범위 구성표 수정
  • 앱 실행

Azure에서 테넌트 만들기

자습서의 지침에 따라 Azure Active Directory B2C 테넌트를 만들어 AAD B2C 테넌트를 만듭니다.

이 문서의 지침을 진행하기 전에 AAD B2C 테넌트에 대한 올바른 디렉터리를 선택했는지 확인합니다.

Azure에서 서버 API 앱 등록

서버 API 앱에 대한 AAD B2C 앱을 등록합니다.

  1. Azure Portal에서 Azure AD B2C로 이동합니다. 사이드바에서 앱 등록을 선택합니다. 새 등록 단추를 선택합니다.
  2. 앱의 이름을 지정합니다(예: Blazor Server AAD B2C).
  3. 지원되는 계정 유형의 경우 다중 테넌트 옵션( ID 공급자 또는 조직 디렉터리의 계정)을 선택합니다(사용자 흐름으로 사용자를 인증하기 위한 경우).
  4. 이 시나리오에서는 서버 API 앱에 리디렉션 URI가 필요하지 않으므로 플랫폼 선택 드롭다운 목록을 선택하지 않은 상태로 두고 리디렉션 URI를 입력하지 마세요.
  5. 권한>openid 및 offline_access 권한에 대한 관리자 동의 허용이 선택되었는지 확인합니다.
  6. 등록을 선택합니다.

다음과 같은 정보를 기록해 둡니다.

  • 서버 API 앱 애플리케이션(클라이언트) ID(예: 41451fa7-82d9-4673-8fa5-69eff5a761fd)
  • AAD B2C 인스턴스(예: 후행 슬래시를 포함하는 https://contoso.b2clogin.com/). 이 인스턴스는 Azure B2C 앱 등록의 스키마 및 호스트로, Azure Portal의 앱 등록 페이지에서 엔드포인트 창을 열어 찾을 수 있습니다.
  • 기본/게시자/테넌트 do기본(예contoso.onmicrosoft.com: ): 등록된 앱에 대한 Azure Portal의 브랜딩 블레이드에서 게시자처럼 할기본 기본 사용할 수 있습니다.

사이드바에서 API 노출을 선택하고 다음 단계를 수행합니다.

  1. 범위 추가를 선택합니다.
  2. 저장하고 계속을 선택합니다.
  3. 범위 이름을 지정합니다(예: API.Access).
  4. 관리자 동의 표시 이름을 지정합니다(예: Access API).
  5. 관리자 동의 설명을 지정합니다(예: Allows the app to access server app API endpoints.).
  6. 상태사용으로 설정되었는지 확인합니다.
  7. 범위 추가를 선택합니다.

다음과 같은 정보를 기록해 둡니다.

  • 앱 ID URI GUID(예: 레코드 41451fa7-82d9-4673-8fa5-69eff5a761fd 원본 https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd)
  • 범위 이름(예: API.Access)

Azure에서 클라이언트 앱 등록

클라이언트 앱에 대한 AAD B2C 앱을 등록합니다.

  1. Azure Portal에서 Azure AD B2C로 이동합니다. 사이드바에서 앱 등록을 선택합니다. 새 등록 단추를 선택합니다.
  2. 앱의 이름을 지정합니다(예: Blazor 클라이언트 AAD B2C).
  3. 지원되는 계정 유형의 경우 다중 테넌트 옵션( ID 공급자 또는 조직 디렉터리의 계정)을 선택합니다(사용자 흐름으로 사용자를 인증하기 위한 경우).
  4. 리디렉션 URI 드롭다운 목록을 SPA(단일 페이지 애플리케이션)로 설정하고 리디렉션 URI 값을 https://localhost/authentication/login-callback제공합니다. Azure 기본 호스트에 대한 프로덕션 리디렉션 URI(예: azurewebsites.net) 또는 사용자 지정 도메인 호스트(예: contoso.com)를 알고 있는 경우 localhost 리디렉션 URI를 제공하는 동시에 프로덕션 리디렉션 URI를 추가할 수도 있습니다. 추가하는 프로덕션 리디렉션 URI에 비 :443 포트에 대한 포트 번호를 포함해야 합니다.
  5. 권한>openid 및 offline_access 권한에 대한 관리자 동의 허용이 선택되었는지 확인합니다.
  6. 등록을 선택합니다.

참고 항목

localhost AAD B2C 리디렉션 URI에 대한 포트 번호를 제공할 필요는 없습니다. 자세한 내용은 리디렉션 URI(회신 URL) 제한 사항: Localhost 예외(Entra 설명서)를 참조하세요.

클라이언트 앱 애플리케이션(클라이언트) ID(예: 4369008b-21fa-427c-abaa-9b53bf58e538)를 기록합니다.

인증>플랫폼 구성>에서 단일 페이지 애플리케이션:

  1. 리디렉션 URI가 https://localhost/authentication/login-callback 있는지 확인합니다.
  2. 암시적 권한 부여 섹션에서 액세스 토큰 및 ID 토큰에 대한 검사box가 선택되지 않았는지 확인합니다. MSAL v2.0 이상을 사용하는 앱에서는 Blazor 암시적 허용이 권장되지 않습니다. 자세한 내용은 보안 ASP.NET Core Blazor WebAssembly를 참조하세요.
  3. 이 환경에서는 앱의 나머지 기본값을 그대로 사용해도 좋습니다.
  4. 변경한 경우 저장 단추를 선택합니다.

사이드바의 API 권한에서:

  1. 사용 권한 추가를 선택하고 내 API를 선택합니다.
  2. 이름 열에서 ‘서버 API 앱’을 선택합니다(예: Blazor Server AAD B2C).
  3. 아직 열려 있지 않은 경우 API 목록을 엽니다.
  4. 검사box를 사용하여 API(예: API.Access)에 대한 액세스를 사용하도록 설정합니다.
  5. 권한 추가를 선택합니다.
  6. {테넌트 이름}에 대한 관리자 동의 허용 단추를 선택합니다. 를 선택하여 확인합니다.

Important

앱 사용에 대한 동의가 사용자에게 위임되므로 API 권한 구성의 마지막 단계에서 테넌트에 관리자 동의를 부여할 권한이 없는 경우 다음 추가 단계를 수행해야 합니다.

  • 앱은 신뢰할 수 있는 게시자 도메인을 사용해야 합니다.
  • Azure Portal의 Server 앱 구성에서 API 공개를 선택합니다. 권한 있는 클라이언트 애플리케이션에서 클라이언트 애플리케이션 추가 단추를 선택합니다. Client 앱의 애플리케이션(클라이언트) ID를 추가합니다(예: 4369008b-21fa-427c-abaa-9b53bf58e538).

Azure Portal에서 Azure AD B2C 로 돌아갑니다. 사용자 흐름을 선택하고 다음 지침을 사용합니다. 등록 및 로그인 사용자 흐름을 만듭니다. 최소한 등록/로그인 사용자 흐름에 대한 애플리케이션 클레임을 선택한 다음, 표시 이름 사용자 특성 검사box를 선택하여 구성 요소(Shared/LoginDisplay.razor)를 LoginDisplay 채웁니다context.User.Identity.Namecontext.User.Identity?.Name/.

앱에 대해 만들어진 가입 및 로그인 사용자 흐름 이름을 기록해 둡니다(예: B2C_1_signupsignin1).

Blazor 앱 만들기

다음 명령에서 자리 표시자를 앞에서 기록해 둔 정보로 바꾸고 명령 셸에서 명령을 실행합니다.

dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C INSTANCE}" --api-client-id "{SERVER API APP CLIENT ID}" --app-id-uri "{SERVER API APP ID URI GUID}" --client-id "{CLIENT APP CLIENT ID}" --default-scope "{DEFAULT SCOPE}" --domain "{TENANT DOMAIN}" -ho -o {PROJECT NAME} -ssp "{SIGN UP OR SIGN IN POLICY}"

Warning

앱 이름 {PROJECT NAME} 에서 OIDC 앱 식별자의 형성을 중단하는 대시(-)를 사용하지 마세요. Blazor WebAssembly 프로젝트 템플릿의 논리는 솔루션 구성에서 OIDC 앱 식별자에 대해 프로젝트 이름을 사용합니다. 파스칼식 대/소문자(BlazorSample) 또는 밑줄(Blazor_Sample)은 사용 가능한 대안입니다. 자세한 내용은 호스트된 Blazor WebAssembly 프로젝트 이름의 대시가 OIDC 보안 중단(dotnet/aspnetcore #35337)을 참조하세요.

자리 표시자 Azure Portal 이름 예시
{AAD B2C INSTANCE} 인스턴스 https://contoso.b2clogin.com/(후행 슬래시 포함)
{PROJECT NAME} BlazorSample
{CLIENT APP CLIENT ID} Client 앱의 애플리케이션(클라이언트) ID 4369008b-21fa-427c-abaa-9b53bf58e538
{DEFAULT SCOPE} 범위 이름 API.Access
{SERVER API APP CLIENT ID} Server 앱의 애플리케이션(클라이언트) ID 41451fa7-82d9-4673-8fa5-69eff5a761fd
{SERVER API APP ID URI GUID} 애플리케이션 ID URI GUID 41451fa7-82d9-4673-8fa5-69eff5a761fd(GUID만, 기본적으로 다음과 일치){SERVER API APP CLIENT ID}
{SIGN UP OR SIGN IN POLICY} 가입/로그인 사용자 흐름 B2C_1_signupsignin1
{TENANT DOMAIN} 주/게시자/테넌트 도메인 contoso.onmicrosoft.com

옵션으로 -o|--output 지정된 출력 위치는 프로젝트 폴더가 존재하지 않는 경우 프로젝트 폴더를 만들고 프로젝트 이름의 일부가 됩니다. OIDC 앱 식별자의 형성을 중단하는 대시(-)를 앱 이름에 사용하지 마세요(앞에 나온 경고 참조).

앱 실행

프로젝트에서 앱을 실행합니다 Server . Visual Studio를 사용하는 경우 다음 중 하나를 수행합니다.

  • 실행 단추 옆에 있는 드롭다운 화살표를 선택합니다. 드롭다운 목록에서 시작 프로젝트 구성을 엽니다. 단일 시작 프로젝트 옵션을 선택합니다. 시작 프로젝트의 프로젝트를 프로젝트로 확인하거나 변경합니다 Server .

  • Server 다음 방법 중 한 가지 방법으로 앱을 시작하기 전에 솔루션 탐색기 프로젝트가 강조 표시되어 있는지 확인합니다.

    • 실행 단추를 선택합니다.
    • 메뉴에서 디버그>디버깅 시작을 사용합니다.
    • F5키를 누릅니다.
  • 명령 셸에서 솔루션의 Server 프로젝트 폴더로 이동합니다. dotnet run 명령을 실행합니다.

사용자 지정 정책

Microsoft 인증 라이브러리(Microsoft.Authentication.WebAssembly.MsalNuGet 패키지)는 기본적으로 AAD B2C 사용자 지정 정책을 지원하지 않습니다.

User.Identity.Name 구성

이 섹션의 지침에서는 필요에 따라 클레임의 값으로 채우는 방법에 User.Identity.Name 대해 name 설명합니다.

기본적으로 Server 앱 API는 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name 클레임 유형의 값을 사용하여 User.Identity.Name을 채웁니다(예: 2d64b3da-d9d5-42c6-9352-53d8df33d770@contoso.onmicrosoft.com).

name 클레임 유형에서 값을 받도록 앱을 구성하려면 다음을 수행합니다.

솔루션의 일부

이 섹션에서는 프로젝트 템플릿에서 Blazor WebAssembly 생성된 솔루션의 부분에 대해 설명하고 솔루션 ClientServer 프로젝트가 참조하도록 구성된 방법을 설명합니다. 연습 섹션의 지침을 사용하여 앱을 만든 경우 기본 작업 애플리케이션에 대해 이 섹션에서 따라야 할 구체적인 지침은 없습니다. 이 섹션의 지침은 사용자를 인증하고 권한을 부여하도록 앱을 업데이트하는 데 유용합니다. 그러나 앱을 업데이트하는 다른 방법은 연습 섹션의 지침에서 새 앱을 만들고 앱의 구성 요소, 클래스 및 리소스를 새 앱으로 이동하는 것입니다.

appsettings.json 구성

‘이 섹션은 솔루션의 Server 앱에 적용됩니다.’

appsettings.json 파일은 액세스 토큰의 유효성을 검사하는 데 사용되는 JWT 전달자 처리기를 구성하기 위한 옵션을 포함합니다.

{
  "AzureAdB2C": {
    "Instance": "https://{TENANT}.b2clogin.com/",
    "ClientId": "{SERVER API APP CLIENT ID}",
    "Domain": "{TENANT DOMAIN}",
    "Scopes": "{DEFAULT SCOPE}",
    "SignUpSignInPolicyId": "{SIGN UP OR SIGN IN POLICY}"
  }
}

예시:

{
  "AzureAdB2C": {
    "Instance": "https://contoso.b2clogin.com/",
    "ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
    "Domain": "contoso.onmicrosoft.com",
    "Scopes": "API.Access",
    "SignUpSignInPolicyId": "B2C_1_signupsignin1",
  }
}

인증 패키지

‘이 섹션은 솔루션의 Server 앱에 적용됩니다.’

Microsoft Identity 플랫폼을 통한 ASP.NET Core Web API에 대한 호출의 권한 부여 및 인증 지원은 Microsoft.Identity.Web 패키지에서 제공합니다.

참고 항목

.NET 앱에 패키지를 추가하는 방법에 대한 지침은 패키지 사용 워크플로에서 패키지 설치 및 관리의 문서(NuGet 설명서)를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

Blazor WebAssembly 템플릿에서 만든 호스트된 Blazor 솔루션의 Server 앱에는 기본적으로 Microsoft.Identity.Web.UI 패키지가 포함됩니다. 패키지는 웹앱에서 사용자 인증을 위한 UI를 추가하며 Blazor 프레임워크에서 사용되지 않습니다. Server 앱을 사용하여 사용자를 직접 인증하지 않는 경우 Server 앱의 프로젝트 파일에서 패키지 참조를 제거하는 것이 안전합니다.

인증 서비스 지원

‘이 섹션은 솔루션의 Server 앱에 적용됩니다.’

AddAuthentication 메서드는 앱 내에서 인증 서비스를 설정하고 JWT 전달자 처리기를 기본 인증 메서드로 구성합니다. AddMicrosoftIdentityWebApi 메서드는 Microsoft Identity 플랫폼 v2.0을 사용하여 웹 API를 보호하도록 서비스를 구성합니다. 이 메서드에는 인증 옵션을 초기화하는 데 필요한 설정을 포함하는 앱 구성의 AzureAdB2C 섹션이 필요합니다.

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C"));

참고 항목

단일 인증 체계가 등록되면 인증 체계가 앱의 기본 체계로 자동으로 사용되며 구성표를 AddAuthentication 또는 AuthenticationOptions을 통해 지정할 필요가 없습니다. 자세한 내용은 ASP.NET Core 인증 개요ASP.NET Core 알림(aspnet/Announcements #490)을 참조하세요.

UseAuthenticationUseAuthorization은 다음을 수행합니다.

  • 앱이 수신 요청에 대해 토큰의 구문 분석 및 유효성 검사를 시도하도록 합니다.
  • 올바른 자격 증명 없이 보호된 리소스에 액세스하려는 요청은 모두 실패하도록 합니다.
app.UseAuthorization();

WeatherForecast 컨트롤러

‘이 섹션은 솔루션의 Server 앱에 적용됩니다.’

컨트롤러(Controllers/WeatherForecastController.cs)는 WeatherForecast 컨트롤러에 적용된 특성을 사용하여 [Authorize] 보호된 API를 노출합니다. 다음과 같은 사항을 이해하는 것이 중요합니다.

  • [Authorize] 이 API 컨트롤러의 특성은 권한 없는 액세스로부터 이 API를 보호하는 유일한 특성입니다.
  • Blazor WebAssembly 앱에서 사용되는 [Authorize] 특성은 앱이 올바르게 작동하려면 사용자에게 권한이 부여되어야 한다는 힌트로만 앱에 제공됩니다.
[Authorize]
[ApiController]
[Route("[controller]")]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAdB2C:Scopes")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        ...
    }
}

wwwroot/appsettings.json 구성

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

구성은 wwwroot/appsettings.json 파일에서 제공합니다.

{
  "AzureAdB2C": {
    "Authority": "{AAD B2C INSTANCE}{TENANT DOMAIN}/{SIGN UP OR SIGN IN POLICY}",
    "ClientId": "{CLIENT APP CLIENT ID}",
    "ValidateAuthority": false
  }
}

위의 구성에서는 {AAD B2C INSTANCE}에 후행 슬래시가 포함됩니다.

예시:

{
  "AzureAdB2C": {
    "Authority": "https://contoso.b2clogin.com/contoso.onmicrosoft.com/B2C_1_signupsignin1",
    "ClientId": "4369008b-21fa-427c-abaa-9b53bf58e538",
    "ValidateAuthority": false
  }
}

인증 패키지

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

앱이 개별 B2C 계정을 사용하도록 만들어진 경우(IndividualB2C) 해당 앱은 자동으로 Microsoft 인증 라이브러리(Microsoft.Authentication.WebAssembly.Msal)에 대한 패키지 참조를 받습니다. 패키지는 앱이 사용자를 인증하고 보호된 API를 호출하는 데 사용할 토큰을 가져올 수 있도록 지원하는 기본 형식 세트를 제공합니다.

앱에 인증을 추가하는 경우에는 Microsoft.Authentication.WebAssembly.Msal 패키지를 앱에 수동으로 추가합니다.

참고 항목

.NET 앱에 패키지를 추가하는 방법에 대한 지침은 패키지 사용 워크플로에서 패키지 설치 및 관리의 문서(NuGet 설명서)를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

Microsoft.Authentication.WebAssembly.Msal 패키지는 타동적으로 Microsoft.AspNetCore.Components.WebAssembly.Authentication 패키지를 앱에 추가합니다.

인증 서비스 지원

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

서버 프로젝트로 요청을 전송할 때 액세스 토큰을 포함하는 HttpClient 인스턴스에 대한 지원이 추가됩니다.

Program 파일에서:

builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{PROJECT NAME}.ServerAPI"));

자리 표시자는 {PROJECT NAME} 솔루션 생성 시 프로젝트 이름입니다. 예를 들어 프로젝트 이름을 BlazorSample 제공하면 이름이 HttpClientBlazorSample.ServerAPI입니다.

사용자 인증에 대한 지원은 Microsoft.Authentication.WebAssembly.Msal 패키지에서 제공하는 AddMsalAuthentication 확장 메서드를 통해 서비스 컨테이너에 등록됩니다. 이 메서드는 앱이 IP(Identity 공급자)와 상호 작용하는 데 필요한 서비스를 설정합니다.

Program 파일에서:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

기본 {SCOPE URI} 액세스 토큰 범위(예: https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access Azure Portal에서 구성한 사용자 지정 URI)입니다.

AddMsalAuthentication 메서드는 콜백을 받아서 앱을 인증하는 데 필요한 매개 변수를 구성합니다. 앱을 구성하는 데 필요한 값은 앱을 등록할 때 Azure Portal AAD 구성에서 얻을 수 있습니다.

액세스 토큰 범위

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

기본 액세스 토큰 범위는 다음과 같은 액세스 토큰 범위 목록을 나타냅니다.

  • 기본적으로 로그인 요청에 포함되어 있음.
  • 인증 직후에 액세스 토큰을 프로비저닝하는 데 사용됨.

모든 범위는 Microsoft Entra ID 규칙에 따라 동일한 앱에 속해야 합니다. 필요에 따라 추가 API 앱에 대한 추가 범위가 추가될 수 있습니다.

builder.Services.AddMsalAuthentication(options =>
{
    ...
    options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

AdditionalScopesToConsent를 사용하여 추가 범위를 지정합니다.

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE URI}");

참고 항목

AdditionalScopesToConsent 사용자가 Microsoft Azure에 등록된 앱을 처음 사용하는 경우 Microsoft Entra ID 동의 UI를 통해 Microsoft Graph에 대한 위임된 사용자 권한을 프로비전할 수 없습니다. 자세한 내용은 ASP.NET Core에서 Graph API 사용을 참조 하세요 Blazor WebAssembly.

기본 액세스 토큰 범위 예제:

options.ProviderOptions.DefaultAccessTokenScopes.Add(
    "https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd/API.Access");

자세한 내용은 ‘추가 시나리오’ 문서의 다음 섹션을 참조하세요.

로그인 모드

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

프레임워크는 기본적으로 팝업 로그인 모드로 설정되며 팝업을 열 수 없는 경우 리디렉션 로그인 모드로 대체됩니다. MsalProviderOptionsLoginMode 속성을 redirect로 설정하여 리디렉션 로그인 모드를 사용하도록 MSAL을 구성합니다.

builder.Services.AddMsalAuthentication(options =>
{
    ...
    options.ProviderOptions.LoginMode = "redirect";
});

기본 설정은 popup이며 문자열 값은 대/소문자를 구분하지 않습니다.

Imports 파일

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

Microsoft.AspNetCore.Components.Authorization 네임스페이스는 _Imports.razor 파일을 통해 앱 전체에서 사용할 수 있게 됩니다.

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

인덱스 페이지

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

인덱스 페이지(wwwroot/index.html)는 JavaScript로 AuthenticationService를 정의하는 스크립트를 포함합니다. AuthenticationService는 OIDC 프로토콜의 하위 수준 세부 정보를 처리합니다. 앱은 내부적으로 스크립트에 정의된 메서드를 호출하여 인증 작업을 수행합니다.

<script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>

App 구성 요소

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

App 구성 요소(App.razor)는 Blazor Server 앱에 있는 App 구성 요소와 비슷합니다.

  • CascadingAuthenticationState 구성 요소는 앱의 나머지 부분에 AuthenticationState를 노출하는 것을 관리합니다.
  • AuthorizeRouteView 구성 요소는 현재 사용자가 지정된 페이지에 액세스할 수 있는 권한이 부여받았는지 확인하고 그러지 않은 경우 RedirectToLogin 구성 요소를 렌더링합니다.
  • RedirectToLogin 구성 요소는 권한 없는 사용자를 로그인 페이지로 리디렉션하는 기능을 관리합니다.

ASP.NET Core 릴리스 간 프레임워크 변경으로 인해 App 구성 요소의 Razor 변경 내용(App.razor)은 이 섹션에 표시되지 않습니다. 지정된 릴리스의 구성 요소의 변경 내용을 검사하려면 다음 방법 중 하나를 사용합니다.

  • 사용하려는 ASP.NET Core 버전의 기본 Blazor WebAssembly 프로젝트 템플릿에서 인증을 위해 프로비전된 앱을 만듭니다. 생성된 앱에서 App 구성 요소(App.razor)를 검사합니다.

  • 참조 소스에서 App 구성 요소(App.razor)를 검사합니다. 분기 선택기에서 버전을 선택하고 수년에 걸쳐 이동했기 때문에 리포지토리 폴더에서 구성 요소를 ProjectTemplates 검색합니다.

    참고 항목

    .NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

RedirectToLogin 구성 요소

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

RedirectToLogin 구성 요소(RedirectToLogin.razor)는 다음을 수행합니다.

  • 로그인 페이지로의 권한 없는 사용자 리디렉션을 관리합니다.
  • 사용자가 액세스를 시도하는 현재 URL은 다음을 사용하여 인증에 성공한 경우 해당 페이지로 반환될 수 있도록 기본.

참조 소스에서 RedirectToLogin 구성 요소를 검사합니다. 구성 요소의 위치는 시간이 지남에 따라 변경되므로 GitHub 검색 도구를 사용하여 구성 요소를 찾습니다.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

LoginDisplay 구성 요소

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

LoginDisplay 구성 요소(LoginDisplay.razor)는 MainLayout 구성 요소(MainLayout.razor)에서 렌더링되고 다음 동작을 관리합니다.

  • 인증된 사용자의 경우:
    • 현재 사용자 이름을 표시합니다.
    • ASP.NET Core Identity에 있는 사용자 프로필 페이지로 연결되는 링크를 제공합니다.
    • 앱에서 로그아웃하는 단추를 제공합니다.
  • 익명 사용자의 경우:
    • 등록 옵션을 제공합니다.
    • 로그인 옵션을 제공합니다.

ASP.NET Core 릴리스 간 프레임워크 변경으로 인해 LoginDisplay 구성 요소의 Razor 변경 내용은 이 섹션에 표시되지 않습니다. 지정된 릴리스의 구성 요소의 변경 내용을 검사하려면 다음 방법 중 하나를 사용합니다.

  • 사용하려는 ASP.NET Core 버전의 기본 Blazor WebAssembly 프로젝트 템플릿에서 인증을 위해 프로비전된 앱을 만듭니다. 생성된 앱에서 LoginDisplay 구성 요소를 검사합니다.

  • 참조 소스에서 LoginDisplay 구성 요소를 검사합니다. 구성 요소의 위치는 시간이 지남에 따라 변경되므로 GitHub 검색 도구를 사용하여 구성 요소를 찾습니다. true와 같은 Hosted에 대한 템플릿 콘텐츠가 사용됩니다.

    참고 항목

    .NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

Authentication 구성 요소

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

Authentication 구성 요소에 의해 생성된 페이지(Pages/Authentication.razor)는 다른 인증 단계를 처리하는 데 필요한 경로를 정의합니다.

RemoteAuthenticatorView 구성 요소는 다음과 같습니다.

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

@code {
    [Parameter]
    public string? Action { get; set; }
}

참고 항목

NRT(Nullable 참조 형식) 및 .NET 컴파일러 null 상태 정적 분석은 .NET 6 이상의 ASP.NET Core에서 지원됩니다. .NET 6에서 ASP.NET Core가 릴리스되기 전에 형식은 string null 형식 지정(?)없이 표시됩니다.

FetchData 구성 요소

‘이 섹션은 솔루션의 Client 앱에 적용됩니다.’

FetchData 구성 요소는 다음을 수행하는 방법을 보여 줍니다.

  • 액세스 토큰을 프로비저닝합니다.
  • 액세스 토큰을 사용하여 ‘서버’ 앱에서 보호된 리소스 API를 호출합니다.

@attribute [Authorize] 지시문은 사용자가 이 구성 요소에 액세스하기 위해 권한을 부여받아야 한다는 것을 Blazor WebAssembly 권한 부여 시스템에 나타냅니다. Client 앱에 특성이 있으면 서버에 있는 API가 적절한 자격 증명 없이 호출되는 것을 방지하지 않습니다. 또한 Server 앱은 적절한 엔드포인트에서 [Authorize]를 사용하여 올바로 보호해야 합니다.

IAccessTokenProvider.RequestAccessToken은 API를 호출하는 요청에 추가할 수 있는 액세스 토큰을 요청하는 작업을 처리합니다. 토큰이 캐시되었거나 서비스에서 사용자 개입 없이 새 액세스 토큰을 프로비저닝할 수 있으면 토큰 요청이 성공합니다. 그러지 않으면 토큰 요청이 실패하고 AccessTokenNotAvailableExceptiontry-catch 문에 catch됩니다.

요청에 포함할 실제 토큰을 가져오려면 앱에서 tokenResult.TryGetToken(out var token)을 호출하여 요청이 성공했는지 확인해야 합니다.

요청이 성공하면 토큰 변수가 액세스 토큰으로 채워집니다. 토큰의 AccessToken.Value 속성은 Authorization 요청 헤더에 포함할 리터럴 문자열을 노출합니다.

사용자 개입 없이 토큰을 프로비저닝할 수 없어 요청이 실패한 경우:

  • .NET 7 이상의 ASP.NET Core: 앱은 지정된 AccessTokenResult.InteractionOptions 토큰을 사용하여 액세스 토큰을 새로 고치도록 탐색 AccessTokenResult.InteractiveRequestUrl 합니다.
  • .NET 6 이하의 ASP.NET Core: 토큰 결과에 리디렉션 URL이 포함됩니다. 이 URL로 이동하면 사용자가 로그인 페이지로 이동하고 인증 성공 후 현재 페이지로 돌아옵니다.
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

문제 해결

로깅

인증에 Blazor WebAssembly 디버그 또는 추적 로깅을 사용하도록 설정하려면 문서 버전 선택기가 ASP.NET Core Blazor 7.0 이상으로 설정된 ASP.NET Core 로깅의 클라이언트 쪽 인증 로깅 섹션을 참조하세요.

일반 오류

  • 앱 또는 IP(Identity 공급자)의 잘못된 구성

    가장 일반적인 오류는 잘못된 구성으로 인해 발생합니다. 다음은 몇 가지 예입니다.

    • 시나리오의 요구 사항에 따라, 누락되거나 잘못된 권한, 인스턴스, 테넌트 ID, 테넌트 도메인, 클라이언트 ID 또는 리디렉션 URI 때문에 앱에서 클라이언트를 인증하지 못합니다.
    • 잘못된 요청 범위는 클라이언트가 서버 웹 API 엔드포인트에 액세스하지 못하게 합니다.
    • 서버 API 권한이 잘못되거나 누락되어 클라이언트가 서버 웹 API 엔드포인트에 액세스할 수 없습니다.
    • IP 앱 등록의 리디렉션 URI에 구성된 것과 다른 포트에서 앱을 실행합니다. Microsoft Entra ID 및 개발 테스트 주소에서 localhost 실행되는 앱에는 포트가 필요하지 않지만 앱의 포트 구성 및 앱이 실행되는 포트는 주소localhost 가 아닌 경우 일치해야 합니다.

    이 문서 지침의 구성 섹션에서는 올바른 구성의 예를 보여 줍니다. 앱 및 IP 구성을 찾는 문서의 각 섹션을 주의 깊게 확인하세요.

    구성이 올바르게 표시되면 다음을 수행합니다.

    • 애플리케이션 로그를 분석합니다.

    • 브라우저의 개발자 도구를 사용하여 클라이언트 앱과 IP 또는 서버 앱 간의 네트워크 트래픽을 검사합니다. 종종 정확한 오류 메시지 또는 문제의 원인에 대한 단서가 있는 메시지가 요청을 수행한 후 IP 또는 서버 앱에 의해 클라이언트로 반환됩니다. 개발자 도구 지침은 다음 문서에서 확인할 수 있습니다.

    • JWT(ON Web Token)가 사용되는 릴리스JS의 Blazor 경우 문제가 발생하는 위치에 따라 클라이언트를 인증하거나 서버 웹 API에 액세스하는 데 사용되는 토큰의 콘텐츠를 디코딩합니다. 자세한 내용은 JWT(JSON Web Token)의 콘텐츠 검사를 참조하세요.

    문서 작업 팀은 설명서 피드백 및 문서의 버그에 응답하지만(이 페이지의 피드백 섹션에서 문제 열기) 제품 지원을 제공할 수 없습니다. 앱 문제 해결을 지원하기 위해 몇 가지 퍼블릭 지원 포럼을 사용할 수 있습니다. 다음을 권장합니다.

    이전 포럼은 Microsoft에서 소유하거나 제어하지 않습니다.

    중요하지 않고 보안이나 기밀이 아니며 재현 가능한 프레임워크 버그 보고서의 경우 ASP.NET Core 제품 단위에서 문제를 여세요. 문제의 원인을 철저하게 조사했으며 자신의 노력과 퍼블릭 지원 포럼에서 커뮤니티의 도움을 받아도 해결할 수 없는 경우에만 제품 단위에서 문제를 열고 그전에는 열지 마세요. 제품 단위는 단순한 구성 오류 또는 타사 서비스와 관련된 사용 사례로 인해 손상된 개별 앱의 문제를 해결할 수 없습니다. 보고서가 본질적으로 민감하거나 기밀이거나 공격자가 악용할 수 있는 제품의 잠재적인 보안 결함을 설명하는 경우 보안 문제 및 버그 보고(dotnet/aspnetcoreGitHub 리포지토리)를 참조하세요.

  • ME-ID에 대한 권한 없는 클라이언트

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] 권한 부여에 실패했습니다. 이러한 요구 사항이 충족되지 않았습니다. DenyAnonymousAuthorizationRequirement: 인증된 사용자가 필요합니다.

    ME-ID의 로그인 콜백 오류:

    • 오류: unauthorized_client
    • 설명: AADB2C90058: The provided application is not configured to allow public clients.

    오류를 해결하려면:

    1. Azure Portal에서 앱의 매니페스트에 액세스합니다.
    2. allowPublicClient 특성null 또는 true로 설정합니다.

Cookie 및 사이트 데이터

Cookie 및 사이트 데이터가 앱을 업데이트할 때에도 유지되어 테스트 및 문제 해결에 방해가 될 수 있습니다. 앱 코드를 변경하거나 공급자를 사용하여 사용자 계정을 변경하거나 공급자 앱 구성을 변경하는 경우 다음을 지우세요.

  • 사용자 로그인 cookie
  • 앱 cookie
  • 캐시되고 저장된 사이트 데이터

남겨진 cookie 및 사이트 데이터로 인해 테스트 및 문제 해결이 지장을 받지 않도록 하려면 다음을 수행합니다.

  • 브라우저 구성
    • 브라우저에서 브라우저를 닫을 때마다 모든 cookie 및 사이트 데이터를 삭제하도록 구성할 수 있는지 테스트합니다.
    • 앱, 테스트 사용자 또는 공급자 구성을 변경할 때 수동으로 또는 IDE를 통해 브라우저를 닫습니다.
  • 사용자 지정 명령을 사용하여 Visual Studio에서 InPrivate 또는 Incognito 모드로 브라우저를 엽니다.
    • Visual Studio의 실행 단추를 통해 브라우저 선택 대화 상자를 엽니다.
    • 추가 단추를 선택합니다.
    • 프로그램 필드에서 브라우저의 경로를 제공합니다. 다음 실행 파일 경로는 Windows 10에서 일반적인 설치 위치입니다. 브라우저가 다른 위치에 설치되어 있거나 Windows 10을 사용하지 않는 경우 브라우저 실행 파일의 경로를 제공합니다.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • 인수 필드에서 브라우저가 InPrivate 또는 Incognito 모드에서 여는 데 사용하는 명령줄 옵션을 제공합니다. 일부 브라우저에는 앱의 URL이 필요합니다.
      • Microsoft Edge: 사용 -inprivate.
      • Google Chrome: 자리 표시자가 {URL} 열 URL인 경우(예https://localhost:5001: )를 사용합니다--incognito --new-window {URL}.
      • Mozilla Firefox: 자리 표시자가 {URL} 열 URL인 경우(예https://localhost:5001: )를 사용합니다-private -url {URL}.
    • 이름 필드에 이름을 입력합니다. 예들 들어 Firefox Auth Testing입니다.
    • 확인 단추를 선택합니다.
    • 앱 테스트를 반복할 때마다 브라우저 프로필을 선택할 필요가 없도록 하려면 기본값으로 설정 단추를 사용하여 프로필을 기본값으로 설정합니다.
    • 앱, 테스트 사용자 또는 공급자 구성을 변경할 때 IDE를 통해 브라우저를 닫습니다.

앱 업그레이드

개발 컴퓨터의 .NET Core SDK 또는 앱 내의 패키지 버전을 업그레이드하거나 앱 내 패키지 버전을 변경한 후 즉시 작동 중인 앱에서 오류가 발생할 수 있습니다. 경우에 따라 중요한 업그레이드를 수행할 때 일관되지 않은 패키지로 인해 응용 프로그램이 중단될 수 있습니다. 이러한 대부분의 문제는 다음 지침에 따라 수정할 수 있습니다.

  1. 명령 셸에서 dotnet nuget locals all --clear를 실행하여 로컬 시스템의 NuGet 패키지 캐시를 지웁니다.
  2. 프로젝트의 binobj 폴더를 삭제합니다.
  3. 프로젝트를 복원하고 다시 빌드합니다.
  4. 앱을 다시 배포하기 전에 서버의 배포 폴더에 있는 모든 파일을 삭제합니다.

참고 항목

앱의 대상 프레임워크와 호환되지 않는 패키지 버전의 사용은 지원되지 않습니다. 패키지에 대한 자세한 내용은 NuGet 갤러리 또는 FuGet 패키지 탐색기를 사용하세요.

Server 실행

호스트된 Blazor WebAssembly솔루션을 테스트하고 문제를 해결할 때 Server 프로젝트에서 앱을 실행하고 있는지 확인합니다.

사용자 검사

다음 User 구성 요소는 앱에서 직접 사용하거나 추가 사용자 지정의 기준으로 사용할 수 있습니다.

User.razor:

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService

<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())
{
    <p class="claim">@(claim.Type): @claim.Value</p>
}

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())
{
    <p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)
{
    <h2>Access token expires</h2>

    <p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>
    <p id="access-token-expires">@AccessToken.Expires</p>

    <h2>Access token granted scopes (as reported by the API)</h2>

    @foreach (var scope in AccessToken.GrantedScopes)
    {
        <p>Scope: @scope</p>
    }
}

@code {
    [CascadingParameter]
    private Task<AuthenticationState> AuthenticationState { get; set; }

    public ClaimsPrincipal AuthenticatedUser { get; set; }
    public AccessToken AccessToken { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        var state = await AuthenticationState;
        var accessTokenResult = await AuthorizationService.RequestAccessToken();

        if (!accessTokenResult.TryGetToken(out var token))
        {
            throw new InvalidOperationException(
                "Failed to provision the access token.");
        }

        AccessToken = token;

        AuthenticatedUser = state.User;
    }

    protected IDictionary<string, object> GetAccessTokenClaims()
    {
        if (AccessToken == null)
        {
            return new Dictionary<string, object>();
        }

        // header.payload.signature
        var payload = AccessToken.Value.Split(".")[1];
        var base64Payload = payload.Replace('-', '+').Replace('_', '/')
            .PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

        return JsonSerializer.Deserialize<IDictionary<string, object>>(
            Convert.FromBase64String(base64Payload));
    }
}

JWT(JSON Web Token)의 콘텐츠 검사

JWT(JSON Web Token)를 디코딩하려면 Microsoft의 jwt.ms 도구를 사용합니다. UI의 값은 브라우저에 남겨지지 않습니다.

인코딩된 JWT 예제(표시를 위해 축약됨):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j ... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Azure AAD B2C에 대해 인증하는 앱용 도구에 의해 디코딩된 JWT 예제:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

추가 리소스