AshAuthentication을 이용하여 API 리소스 보호 구현하기 (1편)

들어가며

Ash 그리고 AshJsonApi 를 통해 리소스를 JSON API 로 제공하는 코드(이전 글 참조)까지 만들어 봤다. 리소스는 외부로 제공되지만 보호 장치가 없어 접근의 제한이 없다. AshAuthentication 을 활용하여 API 리소스를 보호해 보자.

목표

현재 다음 3개의 API 가 보호 없이 서비스되고 있다.

  • POST /api/json/tickets
  • GET/api/json/tickets
  • GET /api/json/tickets/{id}

이 3가지 API를 API key 를 통해 보호하자. 보호 시나리오는 다음과 같다.

  • 요청 헤더에 Authorization: Bearer {API key} 포함 요구
  • API Key 없는 요청은 401 Unauthorized로 차단
  • 유효한 API Key를 가진 요청만 리소스 접근 허용

이번 글 에서는 AshAuthentication 을 설치하고 사용자와 Api Key 를 만들고 이를 검증하는 과정을 살펴 본다.

AshAuthentication 설치

다음 Igniter 명령을 통해 AshAuthentication을 설치한다.

% mix igniter.install ash_authentication_phoenix --auth-strategy api_key

이 명령어 하나로 다음이 자동 생성된다:

  • User 리소스 (lib/helpdesk/accounts/user.ex)
  • ApiKey 리소스 (lib/helpdesk/accounts/api_key.ex)
  • Token 리소스 (JWT 토큰용)
  • 필요한 마이그레이션 파일들

필요에 따라 --auth-strategy 를 추가할 수 있다. 이번엔 api_key를 추가했기 때문에 ApiKey 리소스가 기본으로 만들어 졌다. strategy 는 Password, OAuth2, MagicLink 등 여러가지 전략이 있다.

기존에 User 리소스를 만들지 않았다면 기본 User 리소스와 Token 리소스를 자동으로 만든다.

사용자와 API Key 생성

먼저 User에 create 액션 추가

기본 생성된 User 리소스에는 create 액션이 없으므로 추가해야 한다. create 액션으로 User 레코드를 만들고 ApiKey 를 할당한다.

# lib/helpdesk/accounts/user.ex에 추가
actions do
  defaults [:read]
  
  # 추가할 부분
  create :create do
    primary? true
    accept []  # 빈 사용자만 생성
  end
  
  # ... 기존 액션들 ...
end

iex에서 실제 생성해보기

iex -S mix
# 1. 사용자 생성
iex> {:ok, user} = Helpdesk.Accounts.User 
|> Ash.Changeset.for_create(:create, %{}) 
|> Ash.create(authorize?: false)

# 2. API Key 생성 (1년 유효기간)
iex> {:ok, api_key} = Helpdesk.Accounts.ApiKey 
|> Ash.Changeset.for_create(:create, %{
  user_id: user.id,
  expires_at: DateTime.add(DateTime.utc_now(), 365 * 24 * 60 * 60, :second)
}) 
|> Ash.create(authorize?: false)

# 🔑 Plain text API key는 여기서만 볼 수 있음!
iex> plain_api_key = api_key.__metadata__.plaintext_api_key
"helpdesk_FlhUEreQrABNZ2XvyDIC5bUeJljjHiOGL9jeiwHPpjeMSmYosY4TupfzhIOF339E_ckbhkf"

중요한 포인트

  1. Plain text API key는 생성 직후에만 접근 가능
    • api_key.__metadata__.plaintext_api_key로만 확인 가능
    • 데이터베이스에는 해시된 값만 저장됨
  2. Prefix가 자동으로 붙음
    • helpdesk_로 시작하는 키 생성

API Key 검증

올바른 API Key로 인증 테스트

iex> api_key_string = "helpdesk_FlhUEreQrABNZ2XvyDIC5bUeJljjHiOGL9jeiwHPpjeMSmYosY4TupfzhIOF339E_ckbhkf"

iex> Helpdesk.Accounts.User 
|> Ash.Query.for_read(:sign_in_with_api_key, %{api_key: api_key_string}) 
|> Ash.read_one()

# ✅ 성공 결과
{:ok,
 %Helpdesk.Accounts.User{
   id: "62ff21cf-d1c4-4fd5-8af1-b4cbc160a4c1",
   valid_api_keys: #Ash.NotLoaded<:relationship, field: :valid_api_keys>,
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">
 }}

잘못된 API Key로 테스트

iex> Helpdesk.Accounts.User 
|> Ash.Query.for_read(:sign_in_with_api_key, %{api_key: "invalid_key"}) 
|> Ash.read_one()

# ✅ 실패 결과 (예상됨)
{:error, %Ash.Error.Forbidden{...}}

마무리

리소스 보호를 위해 먼저 AshAuthentication 설치와 User, API key 생성 및 검증까지 완료했다. 다음 글 에서는 실제로 리소스를 보호하는 설정과 만들어진 API key 를 활용하여 리소스에 접근하는 방법을 살펴 보겠다.

댓글 남기기