개발 노트/Backend,CMS,API

[Backend/API] 관리자 인증과 앱 토큰 인증을 분리하는 기준

pposooj 2026. 5. 21. 13:10

Backend/CMS 프로젝트에서는 관리자 화면과 모바일 앱 API가 같은 서버 안에 있는 경우가 많습니다. 관리자 화면은 운영자가 로그인해서 데이터를 등록하고 수정하는 공간이고, 앱 API는 모바일 앱이 공개 데이터나 사용자별 데이터를 조회하는 인터페이스입니다. 둘 다 인증이 필요할 수 있지만 인증의 목적과 방식은 다릅니다.

관리자 인증과 앱 토큰 인증을 분리하지 않으면 보안 경계가 흐려질 수 있습니다. 관리자 세션이 앱 API에서 그대로 통과되거나, 앱 토큰이 관리자 기능에 접근할 수 있으면 큰 문제가 됩니다. 또한 인증 실패와 권한 부족 오류가 뒤섞이면 앱에서 재로그인, 권한 안내, 일반 오류를 구분하기 어렵습니다.

처음에는 같은 서버에서 같은 user table을 쓰기 때문에 인증도 비슷하게 처리할 수 있다고 생각하기 쉽습니다. 하지만 관리자와 앱 사용자는 접근 가능한 기능과 오류 처리 방식이 다르기 때문에 route부터 분리하는 것이 좋습니다.

이 글에서는 Backend 앱 API에서 관리자 인증과 앱 토큰 인증을 분리하는 기준을 정리해보겠습니다. CMS/API 인증 구조를 검토할 때 놓치기 쉬운 route prefix, middleware, token scope, 오류 응답, 로그 처리 기준을 중심으로 살펴보겠습니다.

이 글에서 다룰 문제

인증 경계가 분리되어 있지 않으면 다음 문제가 생길 수 있습니다.

  • 관리자 세션 middleware가 앱 API에 섞인다.
  • 앱 토큰으로 관리자 route에 접근 가능한 구조가 된다.
  • 인증 실패가 HTML 로그인 페이지로 반환되어 앱에서 처리하기 어렵다.
  • 401 인증 실패와 403 권한 부족이 구분되지 않는다.
  • 토큰이나 secret이 로그, 예제 코드, 오류 응답에 남을 수 있다.
  • 관리자 권한 scope와 앱 사용자 scope가 같은 이름으로 섞인다.
  • API route prefix가 명확하지 않아 보안 검수 범위가 흐려진다.

인증은 처음에 정상 동작하는지만 확인하고 넘어가기 쉽습니다. 하지만 실제로 정리해보면 누가 어떤 route에 접근할 수 있는지, 실패했을 때 어떤 응답을 반환할지가 더 중요했습니다. 인증 경계가 흐려지면 나중에 문제를 찾기도 훨씬 어려워지는 것 같습니다.

관리자 인증과 앱 토큰 인증의 차이

관리자 인증과 앱 토큰 인증은 목적이 다릅니다. 관리자 인증은 화면과 운영 권한 중심이고, 앱 토큰 인증은 API 호출과 사용자 권한 중심입니다.

구분 관리자 인증 앱 토큰 인증
주체 운영자, 관리자 계정 모바일 앱 사용자 또는 앱 클라이언트
방식 세션, 쿠키, 관리자 guard Bearer token, API token
접근 대상 CMS 화면, 등록/수정/삭제 앱 API 조회, 사용자별 기능
실패 응답 로그인 화면 또는 관리자 오류 JSON 오류 응답
권한 기준 관리자 역할, 메뉴 권한 사용자 scope, API scope
로그 주의점 관리자 ID 보호 token 원문 기록 금지

같은 인증 테이블을 쓰더라도 middleware와 응답 형식은 분리해야 합니다. 특히 앱 API에서는 HTML 로그인 페이지가 아니라 앱이 처리할 수 있는 JSON 오류 응답을 유지하는 것이 중요합니다.

route prefix부터 분리하기

가장 먼저 route prefix를 분리합니다. route가 섞이면 인증 middleware, 오류 응답, 로그, rate limit 기준도 같이 섞이기 쉽습니다.

/admin/*        관리자 CMS route
/api/v1/*       모바일 앱 API route
/internal/*     내부 서버 간 route

Laravel 기준으로는 route 파일과 middleware group을 나누는 방식이 좋습니다. 아래 코드는 설명용 예시입니다.

// routes/web.php
Route::prefix('admin')
    ->middleware(['web', 'auth:admin'])
    ->group(function () {
        Route::get('/notices', [AdminNoticeController::class, 'index']);
    });

// routes/api.php
Route::prefix('v1')
    ->middleware(['api', 'auth:app-token'])
    ->group(function () {
        Route::get('/me', [AppUserController::class, 'me']);
    });

여기서 중요한 점은 관리자 route와 앱 API route가 서로 다른 인증 middleware를 사용한다는 것입니다. route prefix만 나눠놓고 같은 인증 guard를 쓰면 경계가 충분히 분리되지 않습니다.

처음에는 prefix만 다르면 충분하다고 생각하기 쉽지만 실제 접근 제어는 middleware와 guard에서 결정되기 때문에, prefix와 인증 정책을 함께 나눠야 합니다.

401과 403을 구분하기

앱 API에서는 인증 실패와 권한 부족을 구분해야 합니다. 모든 오류를 401로 처리하면 앱은 사용자가 다시 로그인해야 하는지, 단순히 권한이 없는지 알기 어렵습니다. 반대로 토큰이 없는 상태를 403으로 처리하면 인증 흐름이 헷갈립니다.

상황 HTTP status error code 앱 처리
토큰 없음 401 AUTH_REQUIRED 로그인 필요 안내
토큰 만료 401 AUTH_EXPIRED 토큰 갱신 또는 재로그인
토큰은 있지만 권한 부족 403 FORBIDDEN 권한 없음 안내
관리자 route 접근 시도 403 또는 404 FORBIDDEN 접근 차단

앱에서는 재로그인이 필요한 상황과 권한 안내만 필요한 상황이 다르게 처리될 수 있습니다. 그래서 HTTP status와 error code를 함께 고정해두는 편이 좋습니다.

앱 토큰 scope 설계

앱 토큰에는 필요한 권한만 부여해야 합니다. 관리자 권한을 앱 토큰에 넣으면 안 됩니다.

app:profile:read
app:notice:read
app:favorite:write
app:comment:write

관리자 권한은 별도 체계로 두는 편이 좋습니다.

admin:notice:create
admin:notice:update
admin:user:manage

scope 이름만 봐도 앱 권한과 관리자 권한이 구분되도록 prefix를 나누는 것이 좋습니다. 앱 토큰이 admin:* scope를 가질 수 있는 구조라면 설계 단계에서 다시 확인해야 합니다. 운영이 길어질수록 scope 이름만 보고도 어느 영역의 권한인지 구분되는 구조가 훨씬 안전합니다.

middleware에서 scope 확인하기

final class EnsureAppTokenScope
{
    public function handle($request, Closure $next, string $requiredScope)
    {
        $token = $request->user()?->currentAccessToken();

        if (!$token) {
            return ApiErrorResponse::make(
                'AUTH_REQUIRED',
                '로그인이 필요합니다.',
                401,
                requestId: $request->headers->get('X-Request-Id')
            );
        }

        if (!$token->can($requiredScope)) {
            return ApiErrorResponse::make(
                'FORBIDDEN',
                '접근 권한이 없습니다.',
                403,
                requestId: $request->headers->get('X-Request-Id')
            );
        }

        return $next($request);
    }
}

핵심은 앱 API 오류 응답을 JSON 계약으로 유지하는 것입니다. 관리자 로그인 페이지가 앱 API 응답으로 반환되면 앱에서 처리하기 어렵습니다.

처음에는 framework 기본 인증 실패 응답을 그대로 써도 괜찮아 보일 수 있습니다. 하지만 앱 API에서는 항상 앱이 이해할 수 있는 오류 구조로 반환되도록 맞춰두는 편이 좋습니다.

토큰 로그 제거 기준

토큰은 절대 로그에 원문으로 남기면 안 됩니다. Authorization header, query string, request body에 토큰이 있을 수 있으므로 로깅 전 마스킹해야 합니다.

Authorization: Bearer ***masked***
access_token: ***masked***
refresh_token: ***masked***

로그에는 추적에 필요한 최소 정보만 남기는 것이 좋습니다. 토큰 원문, refresh token, secret처럼 재사용 가능한 인증 정보는 남기지 않아야 합니다.

남겨도 되는 정보 피해야 할 정보
requestId token 원문
userId 일부 또는 내부 식별자 password, secret
route pattern Authorization header 원문
status code refresh token
error code 관리자 URL과 내부 경로

 

관리자 route 보호 기준

앱 토큰이 관리자 route에 접근하지 못하게 하려면 route와 middleware를 명확히 분리해야 합니다. 관리자 route는 가능한 한 앱 클라이언트 설정이나 public 문서에 등장하지 않도록 관리하는 것이 좋습니다.

  • /admin/* route는 auth:admin만 통과하는지 확인한다.
  • /api/v1/* route는 auth:app-token 또는 public policy를 따르는지 확인한다.
  • 앱 토큰에 admin:* scope가 부여되지 않는지 확인한다.
  • 관리자 인증 실패가 앱 API JSON 응답과 섞이지 않는지 확인한다.
  • 관리자 route가 API 문서나 앱 설정에 노출되지 않는지 확인한다.

public API와 인증 API 분리하기

모든 앱 API에 토큰이 필요한 것은 아닐 수 있습니다. 공지사항이나 공개 콘텐츠 목록은 public API로 둘 수 있고, 즐겨찾기나 내 정보는 인증 API로 둘 수 있습니다.

API 유형 인증 필요 여부 예시
공개 콘텐츠 불필요할 수 있음 공지 목록, 배너 목록
사용자별 데이터 필요 내 정보, 즐겨찾기
쓰기 작업 대부분 필요 댓글 작성, 문의 등록
관리자 작업 앱 토큰 불가 CMS 등록/수정/삭제

public API도 rate limit과 응답 필드 제한은 필요합니다. 인증이 없다고 해서 내부 필드를 반환해도 되는 것은 아닙니다.

테스트 기준

인증 분리는 테스트로 확인해야 합니다. 특히 앱 토큰으로 관리자 route에 접근할 수 없는지 확인하는 테스트는 중요합니다.

  • 앱 토큰 없이 인증 API 호출 시 401 JSON 응답이 반환되는지 확인한다.
  • 만료된 토큰은 AUTH_EXPIRED로 처리되는지 확인한다.
  • 권한 없는 scope는 403으로 처리되는지 확인한다.
  • 앱 토큰으로 관리자 route에 접근할 수 없는지 확인한다.
  • 관리자 세션으로 앱 토큰 전용 API가 의도치 않게 통과되지 않는지 확인한다.
  • Authorization header가 로그에 원문으로 남지 않는지 확인한다.
  • 오류 응답에 token, secret, 내부 경로가 포함되지 않는지 확인한다.

자주 하는 실수

1. 관리자와 앱 API를 같은 middleware로 처리하는 경우가 있습니다. 단순해 보이지만 권한 경계가 흐려집니다.

2. 401403을 구분하지 않는 경우도 있습니다. 앱에서는 재로그인 안내와 권한 없음 안내가 달라야 합니다.

3. 토큰 원문을 로그에 남기는 경우가 있습니다. 디버깅할 때 편해 보여도 운영 환경에서는 큰 보안 문제가 될 수 있습니다.

4. 앱 토큰에 관리자 권한을 섞는 경우도 있습니다. 앱 토큰은 앱 API에 필요한 scope만 갖도록 제한해야 합니다.

결론

Backend 앱 API에서 관리자 인증과 앱 토큰 인증은 반드시 분리해야 합니다. 같은 서버와 같은 데이터베이스를 사용하더라도 관리자 CMS와 모바일 앱 API는 접근 목적, 권한 범위, 오류 응답 방식이 다릅니다.

좋은 구조는 route prefix, middleware, scope, 오류 응답을 분리하는 것입니다. /admin/*은 관리자 세션과 관리자 권한을 사용하고, /api/v1/*은 앱 토큰이나 public API 정책을 사용해야 합니다. 앱 토큰에는 관리자 권한을 부여하지 않는 것이 기본입니다.

인증은 처음에 잘 동작하는지만 보기 쉽습니다. 하지만 운영에서는 누가 어디까지 접근할 수 있는지가 더 중요합니다. 인증 경계를 명확히 나누고, 토큰 로그를 제거하며, 401/403 응답을 고정해두는 것이 안전한 API 설계의 시작인 것 같습니다.

참고 자료

  • Laravel Authentication, Authorization, Sanctum/Passport 관련 공식 문서
  • HTTP 401/403 status code와 API 오류 응답 설계 문서
  • OWASP API Security 인증/인가 관련 자료
  • 프로젝트 내부 route prefix, middleware, token scope 문서