ASP.NET Core Empty에서 Identity API 테스트하기

  • 26 minutes to read

Program.cs와 .http 파일로 회원 가입, 로그인, 보호된 API까지 확인하기

ASP.NET Core에서 인증 기능을 붙일 때는 보통 두 가지 방식이 많습니다. Razor Pages UI를 함께 구성하거나, 로그인/회원 가입 API를 직접 작성하는 방식입니다.

ASP.NET Core Identity의 API Endpoint 기능을 사용하면 이 과정을 더 단순하게 만들 수 있습니다. register, login, confirmEmail, forgotPassword, resetPassword, manage/info, manage/2fa 같은 엔드포인트를 빠르게 노출할 수 있기 때문입니다. Identity API 엔드포인트는 SPA나 브라우저 외 클라이언트에서 사용할 수 있도록 제공되며, 이메일 확인과 2단계 인증 같은 기능도 지원합니다.

이 글에서는 가장 작은 형태의 샘플로 시작합니다.

  • 프로젝트 종류: ASP.NET Core Empty

  • 데이터 저장소: EF Core In-Memory Database

  • 사용자 관리: ASP.NET Core Identity API

  • 구성 파일 수: Program.cs 1개 + .http 파일 1개

  • 추가 목표:

    • 사용자 이름은 Email과 동일하게 사용
    • 테스트용 암호는 Pa$$w0rd 사용
    • 앱 시작 시 기본 사용자 자동 생성
    • .http 파일에서 전체 흐름 확인

여기서 사용하는 Pa$$w0rd는 Microsoft 학습용 샘플과 문서에서 자주 보이는 테스트 암호입니다. 실습 문서에서는 익숙한 값이지만, 운영 환경에서는 사용하면 안 됩니다. ASP.NET Core Identity 템플릿과 문서도 일반적으로 사용자 이름과 이메일을 동일하게 다루는 예를 자주 사용합니다.

이 글의 목적은 Identity API의 핵심 흐름을 가장 작은 프로젝트에서 확인하는 것입니다.


1. 프로젝트 만들기

먼저 ASP.NET Core Empty 프로젝트를 생성합니다.

  1. Visual Studio를 실행합니다.
  2. 새 프로젝트 만들기를 선택합니다.
  3. ASP.NET Core Empty 템플릿을 선택합니다.
  4. 프로젝트 이름을 VisualAcademy로 지정합니다.
  5. 원하는 위치를 선택한 뒤 프로젝트를 생성합니다.

이 예제는 구조를 최대한 단순하게 유지합니다. 컨트롤러나 Razor Pages 없이 진행합니다.

2. NuGet 패키지 설치

이 예제에는 다음 패키지가 필요합니다.

  • Microsoft.AspNetCore.Identity.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.InMemory

Visual Studio의 패키지 관리자 콘솔에서 다음 명령을 실행합니다.

Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.InMemory

3. 예제 구성

이번 예제의 흐름은 다음과 같습니다.

  1. EF Core In-Memory Database를 Identity 저장소로 등록

  2. Identity API Endpoint 등록

  3. 앱 시작 시 기본 사용자 자동 생성

  4. /api/secure 같은 보호된 API 작성

  5. .http 파일에서 다음 순서로 테스트

    • 보호된 API 익명 호출 실패
    • 로그인
    • 토큰으로 보호된 API 호출 성공
    • 새 사용자 등록
    • 새 사용자 로그인

운영 환경이라면 SQL Server, PostgreSQL, MySQL, SQLite 같은 실제 데이터베이스를 사용해야 합니다. 하지만 기능 확인과 학습 목적에서는 In-Memory Database가 가장 간단합니다. ASP.NET Core Identity는 일반적으로 영구 저장소를 전제로 설명되지만, 학습용 샘플에서는 다른 저장소를 써도 구조를 이해하는 데는 충분합니다.


4. Program.cs 전체 코드

이제 Program.cs 파일 하나로 전체 구성을 만듭니다.

using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// EF Core In-Memory Database 등록
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("VisualAcademyDb"));

// Identity + API Endpoint 등록
builder.Services
    .AddIdentityApiEndpoints<ApplicationUser>(options =>
    {
        options.User.RequireUniqueEmail = true;

        // 샘플 테스트를 위해 비밀번호 정책을 단순화
        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireUppercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequiredLength = 6;

        // 이 글에서는 로그인 흐름 확인이 목적이므로 이메일 확인을 끔
        options.SignIn.RequireConfirmedEmail = false;
        options.SignIn.RequireConfirmedAccount = false;
    })
    .AddEntityFrameworkStores<AppDbContext>();

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();

// Identity API 자동 매핑
app.MapGroup("/api/identity").MapIdentityApi<ApplicationUser>();

// 공개 API
app.MapGet("/", () => "VisualAcademy Identity API Sample");

// 보호된 API
app.MapGet("/api/secure", (ClaimsPrincipal user) =>
{
    return Results.Ok(new
    {
        Message = "인증된 사용자만 접근할 수 있습니다.",
        UserName = user.Identity?.Name ?? "Unknown"
    });
}).RequireAuthorization();

// 시작 시 기본 사용자 생성
using (var scope = app.Services.CreateScope())
{
    var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();

    const string email = "administrator@visualacademy.com";
    const string password = "Pa$$w0rd";

    var existingUser = await userManager.FindByEmailAsync(email);
    if (existingUser is null)
    {
        var user = new ApplicationUser
        {
            UserName = email,
            Email = email,
            EmailConfirmed = true
        };

        var result = await userManager.CreateAsync(user, password);

        if (!result.Succeeded)
        {
            var errors = string.Join(", ", result.Errors.Select(e => e.Description));
            throw new Exception($"기본 사용자 생성 실패: {errors}");
        }
    }
}

app.Run();

// 사용자 클래스
public class ApplicationUser : IdentityUser
{
}

// EF Core + Identity용 DbContext
public class AppDbContext(DbContextOptions<AppDbContext> options)
    : IdentityDbContext<ApplicationUser>(options)
{
}

5. 코드 설명

코드 양은 많지 않지만, Identity API를 테스트하는 데 필요한 구성이 모두 들어 있습니다.

5.1 In-Memory Database 등록

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("VisualAcademyDb"));
  • EF Core의 In-Memory Database를 사용합니다.
  • 실제 DB 서버 없이도 Identity 저장소를 바로 테스트할 수 있습니다.
  • 앱이 종료되면 데이터는 사라집니다.

즉, 현재 예제에서는 개발용 메모리 저장소를 사용하는 셈입니다.


5.2 Identity API Endpoint 등록

builder.Services
    .AddIdentityApiEndpoints<ApplicationUser>(options =>
    {
        options.User.RequireUniqueEmail = true;

        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireUppercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequiredLength = 6;

        options.SignIn.RequireConfirmedEmail = false;
        options.SignIn.RequireConfirmedAccount = false;
    })
    .AddEntityFrameworkStores<AppDbContext>();

이 부분이 핵심입니다.

  • AddIdentityApiEndpoints<ApplicationUser>()

    • Identity 관련 API를 등록합니다.
  • AddEntityFrameworkStores<AppDbContext>()

    • 사용자 저장소를 EF Core에 연결합니다.

샘플 테스트를 위해 비밀번호 정책은 단순하게 두었습니다. 그래서 Pa$$w0rd 같은 테스트용 암호로 쉽게 실습할 수 있습니다. 실무에서는 길이, 숫자, 특수문자, 대문자/소문자 규칙을 더 엄격하게 두는 편이 일반적입니다. Identity 옵션은 애플리케이션 시작 시 원하는 값으로 변경할 수 있습니다.


5.3 인증/권한 미들웨어

app.UseAuthentication();
app.UseAuthorization();

이 두 줄이 있어야 인증과 권한 검사가 실제로 동작합니다.

  • UseAuthentication() : 요청에 포함된 인증 정보를 확인
  • UseAuthorization() : 현재 사용자가 해당 리소스에 접근 가능한지 검사

Minimal API에서도 인증과 권한 부여는 같은 방식으로 구성합니다.


5.4 Identity API 자동 매핑

app.MapGroup("/api/identity").MapIdentityApi<ApplicationUser>();

이 한 줄로 /api/identity 아래에 Identity 관련 엔드포인트가 자동으로 매핑됩니다.

MapIdentityApi<TUser>가 자동으로 추가하는 엔드포인트는 다음과 같습니다.

  • POST /api/identity/register
  • POST /api/identity/login
  • POST /api/identity/refresh
  • GET /api/identity/confirmEmail
  • POST /api/identity/resendConfirmationEmail
  • POST /api/identity/forgotPassword
  • POST /api/identity/resetPassword
  • GET /api/identity/manage/info
  • POST /api/identity/manage/info
  • POST /api/identity/manage/2fa

기능별로 나누면 다음과 같습니다.

  • 계정 생성 및 로그인

    • POST /api/identity/register
    • POST /api/identity/login
    • POST /api/identity/refresh
  • 이메일 확인 관련

    • GET /api/identity/confirmEmail
    • POST /api/identity/resendConfirmationEmail
  • 비밀번호 재설정 관련

    • POST /api/identity/forgotPassword
    • POST /api/identity/resetPassword
  • 계정 관리

    • GET /api/identity/manage/info
    • POST /api/identity/manage/info
  • 2단계 인증

    • POST /api/identity/manage/2fa

Identity API 엔드포인트는 로그인과 회원 가입만 제공하는 것이 아니라, 이메일 확인, 비밀번호 재설정, 계정 정보 변경, 2단계 인증까지 포함합니다. MapIdentityApi를 사용하면 이러한 JSON API 엔드포인트를 빠르게 추가할 수 있습니다.


5.5 /api/identity 경로를 쓰는 이유

이 글에서는 경로 그룹을 /identity가 아니라 /api/identity로 잡았습니다.

이유는 나중에 메인 프로젝트에 포함했을 때, 기존 Razor Pages Identity UI와 역할을 명확하게 구분하기 위해서입니다. Razor Pages 기반 Identity UI는 일반적으로 Areas/Identity/Pages/... 아래 페이지를 사용하며, 대표적으로 Areas/Identity/Pages/Account/Register, Areas/Identity/Pages/Account/Login, Areas/Identity/Pages/Account/Manage/ChangePassword 같은 구조를 가집니다. 실제 URL도 보통 /Identity/Account/Register 같은 형태가 됩니다.

즉, 아래처럼 구분됩니다.

  • Razor Pages UI: /Identity/Account/Register
  • Identity API: /api/identity/register

기술적으로는 /identity/register를 사용해도 Razor Pages 기본 경로와 바로 충돌하지는 않지만, UI 경로와 API 경로를 분리해 두는 편이 나중에 유지보수하기 더 편합니다.


5.6 보호된 API 작성

app.MapGet("/api/secure", (ClaimsPrincipal user) =>
{
    return Results.Ok(new
    {
        Message = "인증된 사용자만 접근할 수 있습니다.",
        UserName = user.Identity?.Name ?? "Unknown"
    });
}).RequireAuthorization();

이 엔드포인트는 인증된 사용자만 접근할 수 있습니다.

토큰 없이 요청하면 401 Unauthorized가 반환되고, 로그인 후 받은 토큰을 사용하면 정상 응답을 받을 수 있습니다.


5.7 기본 사용자 자동 생성

using (var scope = app.Services.CreateScope())
{
    var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();

    const string email = "administrator@visualacademy.com";
    const string password = "Pa$$w0rd";

    var existingUser = await userManager.FindByEmailAsync(email);
    if (existingUser is null)
    {
        var user = new ApplicationUser
        {
            UserName = email,
            Email = email,
            EmailConfirmed = true
        };

        var result = await userManager.CreateAsync(user, password);

        if (!result.Succeeded)
        {
            var errors = string.Join(", ", result.Errors.Select(e => e.Description));
            throw new Exception($"기본 사용자 생성 실패: {errors}");
        }
    }
}

앱 시작 시 UserManager를 이용해 기본 사용자를 자동 생성합니다.

  • 사용자 이름: administrator@visualacademy.com
  • 이메일: administrator@visualacademy.com
  • 암호: Pa$$w0rd

이 예제에서는 사용자 이름과 이메일을 동일하게 맞췄습니다. 이렇게 두면 로그인 테스트와 관리 API 확인 시 덜 헷갈립니다.

이미 사용자가 존재하면 다시 만들지 않고, 없을 때만 생성합니다. 프로젝트를 실행한 직후 바로 로그인 테스트를 할 수 있도록 하기 위한 구성입니다.


6. .http 파일로 전체 테스트

이제 프로젝트 루트에 VisualAcademy.http 파일을 만들고 아래 내용을 넣습니다.

@VisualAcademy_HostAddress = https://localhost:5001

### 홈 확인
GET {{VisualAcademy_HostAddress}}/

###

### 보호된 API 익명 호출 - 실패
GET {{VisualAcademy_HostAddress}}/api/secure

###

### 기본 사용자 로그인
# @name login
POST {{VisualAcademy_HostAddress}}/api/identity/login?useCookies=false
Content-Type: application/json

{
  "email": "administrator@visualacademy.com",
  "password": "Pa$$w0rd"
}

###

### 로그인 응답에서 accessToken 추출
@accessToken = {{login.response.body.$.accessToken}}

### 보호된 API 인증 호출 - 성공
GET {{VisualAcademy_HostAddress}}/api/secure
Authorization: Bearer {{accessToken}}

###

### 새 사용자 등록
POST {{VisualAcademy_HostAddress}}/api/identity/register
Content-Type: application/json

{
  "email": "testuser@visualacademy.com",
  "password": "Pa$$w0rd"
}

###

### 새 사용자 로그인
# @name login2
POST {{VisualAcademy_HostAddress}}/api/identity/login?useCookies=false
Content-Type: application/json

{
  "email": "testuser@visualacademy.com",
  "password": "Pa$$w0rd"
}

###

@accessToken2 = {{login2.response.body.$.accessToken}}

### 새 사용자로 보호된 API 호출
GET {{VisualAcademy_HostAddress}}/api/secure
Authorization: Bearer {{accessToken2}}

###

7. .http 실행 순서

프로젝트를 실행한 뒤 .http 파일의 각 요청을 순서대로 실행합니다.

7.1 홈 확인

GET /

문자열 응답이 정상적으로 오면 애플리케이션이 실행 중인 상태입니다.


7.2 보호된 API 익명 호출

GET /api/secure

이 요청은 인증 없이 호출하므로 실패해야 합니다.

예상 결과:

401 Unauthorized

이 응답이 나오면 보호된 API가 제대로 잠겨 있다는 뜻입니다.


7.3 기본 사용자 로그인

POST /api/identity/login?useCookies=false

본문:

{
  "email": "administrator@visualacademy.com",
  "password": "Pa$$w0rd"
}

성공하면 응답 본문에 accessToken이 반환됩니다.

예시:

{
  "tokenType": "Bearer",
  "accessToken": "...",
  "expiresIn": 3600,
  "refreshToken": "..."
}

7.4 토큰으로 보호된 API 호출

GET /api/secure
Authorization: Bearer {{accessToken}}

로그인 후 받은 토큰을 포함하므로 이번에는 성공해야 합니다.

예상 응답:

{
  "message": "인증된 사용자만 접근할 수 있습니다.",
  "userName": "administrator@visualacademy.com"
}

7.5 새 사용자 등록

POST /api/identity/register

본문:

{
  "email": "testuser@visualacademy.com",
  "password": "Pa$$w0rd"
}

정상적으로 등록되면 성공 응답이 반환됩니다.


7.6 새 사용자 로그인

이제 방금 만든 사용자로 로그인합니다.

POST /api/identity/login?useCookies=false

본문:

{
  "email": "testuser@visualacademy.com",
  "password": "Pa$$w0rd"
}

성공하면 새 토큰이 발급됩니다.


7.7 다른 엔드포인트도 확인 가능

이 글에서는 핵심 흐름만 테스트했지만, 필요하면 아래 엔드포인트도 같은 방식으로 확인할 수 있습니다.

  • GET /api/identity/confirmEmail
  • POST /api/identity/resendConfirmationEmail
  • POST /api/identity/forgotPassword
  • POST /api/identity/resetPassword
  • GET /api/identity/manage/info
  • POST /api/identity/manage/info
  • POST /api/identity/manage/2fa

이 중 이메일 확인, 비밀번호 재설정, 2단계 인증은 실제로 사용하려면 이메일 발송기나 인증기 앱 같은 추가 구성이 필요할 수 있습니다.


8. useCookies=false를 붙인 이유

로그인 요청은 다음과 같이 보냈습니다.

POST /api/identity/login?useCookies=false

이 옵션을 주는 이유는 쿠키가 아니라 Bearer Token 기반 응답을 받기 위해서입니다.

모바일 앱, Flutter 앱, MAUI 앱, 일반 API 테스트에서는 쿠키보다 토큰 방식이 더 다루기 쉽습니다.

웹 브라우저 중심 애플리케이션이라면 쿠키 기반 인증이 더 자연스러울 수 있고, 앱이나 외부 클라이언트에서는 토큰 기반이 더 어울리는 경우가 많습니다.


9. 이 예제의 장점

이 샘플은 작은 프로젝트지만 확인할 수 있는 범위가 넓습니다.

9.1 파일 수가 적음

  • Program.cs
  • .http

핵심 흐름은 거의 이 두 파일에 들어 있습니다.

9.2 마이그레이션이 필요 없음

In-Memory Database를 사용하므로 SQL Server나 SQLite 설정 없이 바로 실행할 수 있습니다.

9.3 Identity API를 빠르게 확인할 수 있음

로그인 API, 회원 가입 API, 이메일 확인 API, 비밀번호 재설정 API, 계정 관리 API를 직접 작성하지 않아도 MapIdentityApi만으로 기본 동작을 확인할 수 있습니다.

9.4 앱 백엔드 테스트에 잘 맞음

쿠키가 아니라 Bearer Token 기반 호출 흐름을 연습하기에 적합합니다. MAUI, Flutter, Android, iOS 같은 앱 백엔드를 빠르게 실험할 때도 방향을 잡기 좋습니다.


10. 운영 코드로 바로 쓰면 안 되는 이유

이 샘플은 학습용과 테스트용으로는 적절하지만, 운영 코드로 그대로 사용하면 안 됩니다.

10.1 In-Memory Database 사용

앱이 종료되면 데이터가 모두 사라집니다. 운영에서는 실제 데이터베이스를 사용해야 합니다.

10.2 기본 사용자 하드코딩

const string email = "administrator@visualacademy.com";
const string password = "Pa$$w0rd";

샘플에서는 편하지만, 운영에서는 위험합니다. 초기 관리자 계정이 필요하다면 배포 시점 시드 데이터, 환경 변수, 비밀 저장소, 별도 관리자 초기화 절차 등을 사용하는 편이 낫습니다.

10.3 이메일 확인 비활성화

options.SignIn.RequireConfirmedEmail = false;

이 예제는 로그인 흐름을 바로 확인하는 것이 목적이라 이메일 확인을 끈 상태입니다. 운영 환경에서는 보통 이메일 확인을 필수로 둡니다. Identity API는 이메일 확인 관련 엔드포인트도 제공합니다.

10.4 회원 가입 API 남용 방어 없음

실제 서비스에서는 다음과 같은 요소도 고려해야 합니다.

  • Rate Limiting
  • CAPTCHA
  • Email Confirm
  • 로깅 및 모니터링
  • 가입 정책 제한

회원 가입 엔드포인트는 외부에 공개되기 때문에, 단순히 API가 동작하는 것만 확인하고 끝내면 부족합니다.


11. ApplicationUserApplicationRole로 확장하기

앞에서는 가장 단순한 형태로 ApplicationUser만 사용하는 구성을 살펴봤습니다. 실제 프로젝트에서는 여기에 역할(Role) 개념까지 함께 두는 경우가 많습니다. 예를 들어 관리자, 운영자, 일반 사용자처럼 사용자 권한을 구분해야 하는 경우입니다.

이럴 때는 ApplicationUser뿐 아니라 ApplicationRole 클래스도 함께 정의해서 Role Base 기반 Identity 구조로 확장해 두면 이후 기능 확장이 편합니다.

가장 단순한 형태는 아래와 같습니다.

public class ApplicationUser : IdentityUser
{
}

public class ApplicationRole : IdentityRole
{
}

지금은 추가 속성이 없어도 괜찮습니다. 중요한 점은 처음부터 IdentityUser, IdentityRole를 직접 사용하는 대신, 프로젝트 전용 타입인 ApplicationUser, ApplicationRole로 감싸 두는 것입니다.

이렇게 해 두면 나중에 아래와 같은 확장이 쉬워집니다.

  • ApplicationUser

    • DisplayName
    • Department
    • TenantId
    • CreatedAt
  • ApplicationRole

    • Description
    • DisplayName
    • IsSystemRole

즉, 지금 당장은 빈 클래스라도 괜찮지만, 나중에 바꿔야 할 가능성이 있다면 처음부터 분리해 두는 편이 낫습니다.

Role Base 기반으로 가려면 DbContext도 사용자와 역할을 모두 인식하도록 바꿔 줍니다.

public class AppDbContext(DbContextOptions<AppDbContext> options)
    : IdentityDbContext<ApplicationUser, ApplicationRole, string>(options)
{
}

그리고 Identity 등록 부분도 역할 지원이 포함되도록 바꿉니다.

builder.Services
    .AddIdentityApiEndpoints<ApplicationUser>(options =>
    {
        options.User.RequireUniqueEmail = true;

        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireUppercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequiredLength = 6;

        options.SignIn.RequireConfirmedEmail = false;
        options.SignIn.RequireConfirmedAccount = false;
    })
    .AddRoles<ApplicationRole>()
    .AddEntityFrameworkStores<AppDbContext>();

여기서 핵심은 AddRoles<ApplicationRole>()입니다. 이 설정이 있어야 RoleManager<ApplicationRole>를 주입받아 역할을 만들고 관리할 수 있습니다.

예를 들어 앱 시작 시 기본 역할과 기본 관리자를 함께 만드는 식으로 확장할 수 있습니다.

using (var scope = app.Services.CreateScope())
{
    var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
    var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<ApplicationRole>>();

    const string adminRoleName = "Administrators";
    const string email = "administrator@visualacademy.com";
    const string password = "Pa$$w0rd";

    if (!await roleManager.RoleExistsAsync(adminRoleName))
    {
        var roleResult = await roleManager.CreateAsync(new ApplicationRole
        {
            Name = adminRoleName
        });

        if (!roleResult.Succeeded)
        {
            var errors = string.Join(", ", roleResult.Errors.Select(e => e.Description));
            throw new Exception($"기본 역할 생성 실패: {errors}");
        }
    }

    var existingUser = await userManager.FindByEmailAsync(email);
    if (existingUser is null)
    {
        var user = new ApplicationUser
        {
            UserName = email,
            Email = email,
            EmailConfirmed = true
        };

        var userResult = await userManager.CreateAsync(user, password);

        if (!userResult.Succeeded)
        {
            var errors = string.Join(", ", userResult.Errors.Select(e => e.Description));
            throw new Exception($"기본 사용자 생성 실패: {errors}");
        }

        var addToRoleResult = await userManager.AddToRoleAsync(user, adminRoleName);

        if (!addToRoleResult.Succeeded)
        {
            var errors = string.Join(", ", addToRoleResult.Errors.Select(e => e.Description));
            throw new Exception($"기본 사용자 역할 할당 실패: {errors}");
        }
    }
}

이렇게 구성하면 다음과 같은 흐름이 만들어집니다.

  1. Administrators 역할이 없으면 먼저 생성
  2. 기본 사용자 administrator@visualacademy.com이 없으면 생성
  3. 생성한 사용자를 Administrators 역할에 추가

이제 보호된 API도 단순 인증뿐 아니라 역할 기반으로 제한할 수 있습니다.

예를 들어 현재는 이렇게 되어 있습니다.

app.MapGet("/api/secure", (ClaimsPrincipal user) =>
{
    return Results.Ok(new
    {
        Message = "인증된 사용자만 접근할 수 있습니다.",
        UserName = user.Identity?.Name ?? "Unknown"
    });
}).RequireAuthorization();

여기서 역할 제한을 주고 싶다면 아래처럼 바꿀 수 있습니다.

app.MapGet("/api/admin", (ClaimsPrincipal user) =>
{
    return Results.Ok(new
    {
        Message = "Administrators 역할 사용자만 접근할 수 있습니다.",
        UserName = user.Identity?.Name ?? "Unknown"
    });
}).RequireAuthorization(new AuthorizeAttribute { Roles = "Administrators" });

또는 정책 기반으로 따로 등록해서 사용할 수도 있습니다.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireAdministrators", policy =>
    {
        policy.RequireRole("Administrators");
    });
});

그리고 엔드포인트에서 이렇게 적용할 수 있습니다.

app.MapGet("/api/admin", (ClaimsPrincipal user) =>
{
    return Results.Ok(new
    {
        Message = "Administrators 정책을 통과한 사용자입니다.",
        UserName = user.Identity?.Name ?? "Unknown"
    });
}).RequireAuthorization("RequireAdministrators");

정리하면, 현재 글의 샘플은 ApplicationUser만으로도 충분히 동작합니다. 다만 실제 프로젝트로 확장할 가능성이 있다면 처음부터 아래 구조를 잡아 두는 편이 더 자연스럽습니다.

  • ApplicationUser : IdentityUser
  • ApplicationRole : IdentityRole
  • IdentityDbContext<ApplicationUser, ApplicationRole, string>
  • AddRoles<ApplicationRole>()
  • RoleManager<ApplicationRole>UserManager<ApplicationUser>를 이용한 초기 역할/사용자 구성

이 구조로 바꿔 두면 이후 관리자 페이지, 역할별 메뉴 노출, 관리자 전용 API, 멀티 테넌트 권한 분리 같은 기능으로 확장하기가 훨씬 쉬워집니다.


12. 마무리

이 글에서는 ASP.NET Core Empty 프로젝트에서 시작해서 다음 흐름을 한 번에 확인했습니다.

  • EF Core In-Memory Database 등록
  • ASP.NET Core Identity API Endpoint 구성
  • ApplicationUser 사용
  • 기본 사용자 자동 생성
  • /api/identity 경로 그룹 적용
  • MapIdentityApi의 전체 엔드포인트 목록 정리
  • 보호된 API 작성
  • .http 파일로 로그인/등록/보호된 API 테스트

핵심은 다음과 같습니다.

Identity API를 사용하면 인증 관련 엔드포인트를 직접 반복 구현하지 않고도 회원 가입, 로그인, 토큰 기반 인증, 이메일 확인, 비밀번호 재설정, 계정 관리, 2단계 인증 흐름을 빠르게 구성하고 테스트할 수 있습니다.

학습용, 데모용, 프로토타입용으로는 특히 효율적입니다. 반대로 운영 코드로 가져갈 때는 데이터 저장소, 이메일 확인, 가입 제한, 남용 방어 같은 요소를 별도로 정리해야 합니다.


정리

Program.cs

  • Identity API 등록
  • ApplicationUser 사용
  • In-Memory Database 사용
  • 기본 사용자 자동 생성
  • /api/identity 경로 그룹 적용
  • 보호된 API 추가

VisualAcademy.http

  • 익명 호출 실패 확인
  • 기본 사용자 로그인
  • 토큰으로 보호된 API 호출
  • 새 사용자 등록
  • 새 사용자 로그인

이 두 파일만으로 Identity API의 기본 흐름을 대부분 확인할 수 있습니다.

더 깊이 공부하고 싶다면
DevLec에서는 실무 중심의 C#, .NET, ASP.NET Core, Blazor, 데이터 액세스 강좌를 단계별로 제공합니다. 현재 수강 가능한 강좌 외에도 더 많은 과정이 준비되어 있습니다.
DevLec.com에서 자세한 커리큘럼을 확인해 보세요.
DevLec 공식 강의
C# Programming
C# 프로그래밍 입문
프로그래밍을 처음 시작하는 입문자를 위한 C# 기본기 완성 과정입니다.
ASP.NET Core 10.0
ASP.NET Core 10.0 시작하기 MVC Fundamentals Part 1 MVC Fundamentals Part 2
웹 애플리케이션의 구조와 MVC 패턴을 ASP.NET Core로 실습하며 익힐 수 있습니다.
Blazor Server
풀스택 웹개발자 과정 Part 1 풀스택 웹개발자 과정 Part 2 풀스택 웹개발자 과정 Part 3
실무에서 바로 활용 가능한 Blazor Server 기반 관리자·포털 프로젝트를 만들어 봅니다.
Data & APIs
Entity Framework Core 시작하기 ADO.NET Fundamentals Blazor Server Fundamentals Minimal APIs
데이터 액세스와 Web API를 함께 이해하면 실무 .NET 백엔드 개발에 큰 도움이 됩니다.
VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com