본문 바로가기
C.W.K.
Stream
Lesson 01 of 06 · published

Basic과 Bearer — 자격 증명을 싣는 두 가지 방식

~10 min · auth-security, basic, bearer, authorization

Level 0HTTP 입문자
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"JWT, OAuth, API 키, 세션 쿠키, AWS SigV4는 구조가 서로 달라도 결국 HTTP 요청에 자격 증명이나 요청 증명을 담아 보내는 방식이야. Basic과 Bearer를 익히면 그중 가장 널리 쓰이는 Authorization 헤더의 문법부터 잡을 수 있어."

Authorization 헤더 — 하나의 헤더, 여러 인증 스킴

HTTP는 인증 자격 증명을 전달하는 Authorization 헤더를 정의해. 값은 <scheme> <credentials> 형태이며, 스킴 이름이 뒤따르는 자격 증명을 어떻게 해석할지 서버에 알려줘.

BasicBearer는 대표적인 인증 스킴이야. OAuth 2.0 액세스 토큰은 흔히 Bearer로 보내고, JWT도 Bearer 토큰으로 전달할 수 있어. 하지만 모든 OAuth 토큰이나 JWT가 반드시 Bearer인 건 아니고, 토큰의 형식·수명·검증 방식도 전송 스킴과는 별개의 문제야.

HTTP Basic — 오래되고 단순한 방식

Basic은 사용자 이름과 비밀번호를 매 요청에 직접 실어 보내:

Authorization: Basic cGlwcGE6c2VjcmV0MTIz

자격 증명 부분은 base64("username:password")이며, 위 예에서는 pippa:secret123이야. Base64는 인코딩일 뿐 암호화가 아니므로, 헤더를 읽을 수 있는 주체는 누구나 원래 값을 복원할 수 있어. 자격 증명을 기록한 로그나 TLS가 끝난 뒤의 프록시도 노출 지점이 될 수 있어.

TLS 없이 Basic을 사용하면 네트워크 경로에서 비밀번호가 그대로 노출돼. TLS로 보호한 내부 도구나 제한된 환경에서 쓸 수는 있지만, 모든 요청에 장기 자격 증명이 반복해서 포함되므로 저장·기록·중계 과정까지 엄격히 관리해야 해. 운영 API에서는 대개 더 짧은 수명과 좁은 권한을 가진 토큰을 선택해.

HTTP Bearer — 비밀번호 대신 토큰 전달하기

Bearer는 토큰을 자격 증명으로 실어 보내:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1XzQyIn0.signature

토큰은 로그인 엔드포인트나 OAuth 권한 부여 흐름 같은 선행 절차에서 발급돼. 불투명한 무작위 문자열일 수도 있고, 위 예처럼 JWT일 수도 있어. 서버는 저장소 조회, JWT 검증, 토큰 검사 엔드포인트 등 알맞은 방법으로 토큰의 유효성과 권한을 확인해. 사용자의 원래 비밀번호는 각 API 요청에 포함되지 않아.

장점은 이래:

  • 비밀번호와 분리할 수 있어. 토큰이 노출돼도 사용자 비밀번호 자체가 드러난 것은 아니므로 해당 토큰을 중심으로 대응할 수 있어.
  • 폐기 정책을 설계할 수 있어. 서버 저장형 토큰은 즉시 비활성화하기 쉽고, 자체 포함형 토큰은 짧은 만료 시간이나 거부 목록 같은 별도 전략이 필요해.
  • 수명을 짧게 정할 수 있어. 액세스 토큰을 몇 분 단위로 만료시키면 누출 뒤 악용 가능한 시간을 줄일 수 있어.
  • 권한을 좁힐 수 있어. 토큰에 특정 범위와 대상만 허용하면 원래 계정 권한 전체를 넘기지 않아도 돼.

이런 성질 덕분에 많은 현대 API가 Bearer 토큰을 사용해. 토큰 내부 형식은 다음 과에서 더 자세히 다룰게. 무작위 문자열이나 JWT처럼 형식이 달라도 실제 전송에서는 Authorization: Bearer <뭐든> 모양을 공유해. 단, 토큰을 가진 누구나 그 권한을 행사할 수 있으므로 TLS와 안전한 저장은 필수야.

WWW-Authenticate — 서버가 보내는 인증 챌린지

클라이언트가 자격 증명을 보내지 않았거나 유효하지 않은 자격 증명을 보냈다면, 서버는 사용할 인증 스킴과 오류 정보를 담은 WWW-Authenticate 응답 헤더와 함께 401 Unauthorized를 반환해:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"
Content-Type: application/json

{"error": {"code": "unauthenticated", "message": "..."}}

브라우저는 Basic 챌린지를 받으면 로그인 대화상자를 표시할 수 있고, 프로그램 클라이언트는 헤더를 읽어 필요한 인증 스킴과 실패 원인을 판단할 수 있어. API 문서가 인증 방식을 설명하더라도, 오류 응답에는 프로토콜이 정한 챌린지를 정확히 포함하는 편이 좋아.

Basic과 Bearer를 이해하면 Authorization 헤더 기반 인증의 공통 문법이 보여. JWT와 OAuth 액세스 토큰, 일부 API 키는 Bearer로 전달할 수 있고, 세션은 흔히 Cookie 헤더를 사용해. AWS SigV4처럼 요청 자체에 서명하는 방식은 Bearer와 다르지만, 어떤 자격 증명을 어떻게 만들고 검증하며 만료·폐기할지 따져야 한다는 핵심 질문은 같아.

사용자 정의 스킴 — Basic과 Bearer로 부족할 때

일부 API는 고유한 스킴이나 헤더를 사용해. AWS의 Authorization: AWS4-HMAC-SHA256 ...은 요청 구성 요소에 서명해 무결성과 자격 증명을 함께 검증하는 방식이야. Cloudflare의 X-Auth-Key처럼 별도 헤더를 쓰는 API도 있고, Stripe처럼 API 키를 Bearer 스킴에 담는 API도 있어. 별도 헤더 사용 자체가 RFC 위반은 아니지만, 표준 HTTP 인증 프레임워크의 챌린지·처리 관례를 자동으로 얻지는 못해.

새 스킴을 만들기 전에는 정말 필요한지 먼저 따져봐. 표준 스킴은 클라이언트 라이브러리와 프록시, 게이트웨이, 비밀값 가림 처리의 지원을 받기 쉬워. 반면 요청 서명이나 송신자 제한 토큰처럼 Bearer가 제공하지 못하는 보안 속성이 필요하다면 검증된 표준 방식을 선택해야 해.

cwkPippa의 인증 구성

cwkPippa API는 로그인 때 발급한 Bearer 토큰을 사용해. PIN 기반 위협 모델의 전체 내용은 solo-auth-quest에서 다뤄. 토큰은 JWT가 아니라 만료 시각과 함께 SQLite에 저장하는 무작위 문자열이라 서버에서 직접 폐기하기 쉬워. 내부 관리자 엔드포인트에는 별도의 Authorization: Bearer <admin-token> 검사도 적용해. 프런트엔드는 사용자 토큰을 HttpOnly 쿠키에 보관하고, 백엔드는 FastAPI 의존성 주입으로 읽어. 표준 전송 형태를 활용하면서 인증 방식의 수는 최소화한 구성이야.

Code

요청에서 비교하는 Basic과 Bearer, 그리고 WWW-Authenticate 챌린지·bash
# HTTP Basic — username:password 가 (base64 아래) 명료
# echo -n 'pippa:secret123' | base64 → cGlwcGE6c2VjcmV0MTIz
curl -H 'Authorization: Basic cGlwcGE6c2VjcmV0MTIz' \
     https://api.example.com/account

# 동등 curl 단축:
curl -u pippa:secret123 https://api.example.com/account

# HTTP Bearer — opaque token
curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...' \
     https://api.example.com/account

# 자격 증명 누락/잘못됐을 때 server 의 auth 챌린지
curl -i https://api.example.com/account
# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Bearer realm="api"
# Content-Type: application/json
FastAPI Bearer 인증 의존성 — WWW-Authenticate를 포함한 401·python
# Server 쪽 — FastAPI 의 HTTP Bearer dependency
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

app = FastAPI()
bearer = HTTPBearer()  # 'Authorization: Bearer <token>' 읽거나 401 돌려줌

# 가상 token store — production: Redis, SQLite, 혹은 JWT 검증
_tokens: dict[str, str] = {'abc.def.ghi': 'u_42'}

async def current_user(creds: HTTPAuthorizationCredentials = Depends(bearer)) -> str:
    token = creds.credentials
    user_id = _tokens.get(token)
    if not user_id:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail='invalid or expired token',
            headers={'WWW-Authenticate': 'Bearer'},  # client 한테 뭘 보낼지 알려줘
        )
    return user_id

@app.get('/account')
async def my_account(uid: str = Depends(current_user)):
    return {'user_id': uid, 'role': 'daughter'}
클라이언트 구성: Bearer 헤더와 httpx Basic 인증 튜플·python
# Client 쪽 — 영속 bearer token 가진 httpx
import httpx

class APIClient:
    def __init__(self, base_url: str, token: str):
        # Header 가 client 에 한 번 설정되고 모든 request 에 재사용
        self.client = httpx.Client(
            base_url=base_url,
            headers={'Authorization': f'Bearer {token}'},
        )

    def me(self) -> dict:
        return self.client.get('/account').json()

    def refresh_token(self, new_token: str) -> None:
        # Token rotate 시 in place update
        self.client.headers['Authorization'] = f'Bearer {new_token}'

# Basic auth 대안 (httpx)
client_basic = httpx.Client(auth=('pippa', 'secret123'))

External links

Exercise

FastAPI 서버에 Bearer로 보호하는 GET /account와 Basic으로 보호하는 GET /admin을 만들어. 인증에 실패하면 두 엔드포인트 모두 401과 알맞은 WWW-Authenticate 응답 헤더를 반환해야 해. 각 엔드포인트를 유효한 자격 증명, 누락된 Authorization 헤더, 잘못된 자격 증명으로 한 번씩 호출해 결과를 비교해봐. 보너스로 로컬 실습 환경의 평문 HTTP에서 Basic 인증을 호출하고 curl -v로 요청 헤더를 관찰해. Base64로 인코딩된 비밀번호를 쉽게 복원할 수 있다는 점을 확인하되, 이런 구성을 외부 네트워크나 운영 환경에는 절대 배포하지 마.
Hint
FastAPI의 HTTPBearer()HTTPBasic() 보안 의존성이 자격 증명 파싱을 맡아줘. 검증에 실패할 때는 headers={'WWW-Authenticate': 'Bearer'} 또는 Basic realm="admin"을 담은 401을 반환하면 돼. 평문 HTTP 데모는 http://localhost:8000에서 uvicorn을 실행한 뒤 curl에 -u user:pass -v를 붙여 호출해. 요청의 Authorization: Basic dXNlcjpwYXNz를 확인하고, dXNlcjpwYXNz를 Base64로 디코딩하면 user:pass가 나온다는 점을 살펴봐.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.