본문으로 건너뛰기

API 서비스 또는 백엔드에서 액세스 토큰 (Access token) 검증하는 방법

액세스 토큰 (Access token) 검증은 Logto에서 역할 기반 접근 제어 (RBAC)를 적용하는 데 중요한 부분입니다. 이 가이드에서는 백엔드 / API에서 Logto가 발급한 JWT를 검증하는 방법, 서명, 발급자 (Issuer), 대상 (Audience), 만료, 권한 (스코프), 조직 컨텍스트 등을 확인하는 방법을 안내합니다.

시작하기 전에

1단계: 상수 및 유틸리티 초기화

토큰 추출 및 검증을 처리하기 위해 코드에서 필요한 상수와 유틸리티를 정의하세요. 유효한 요청에는 Authorization 헤더가 Bearer <access_token> 형식으로 포함되어야 합니다.

auth-middleware.ts
import { IncomingHttpHeaders } from 'http';

const JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks';
const ISSUER = 'https://your-tenant.logto.app/oidc';

export class AuthInfo {
constructor(
public sub: string,
public clientId?: string,
public organizationId?: string,
public scopes: string[] = [],
public audience: string[] = []
) {}
}

export class AuthorizationError extends Error {
name = 'AuthorizationError';
constructor(
message: string,
public status = 403
) {
super(message);
}
}

export function extractBearerTokenFromHeaders({ authorization }: IncomingHttpHeaders): string {
const bearerPrefix = 'Bearer ';

if (!authorization) {
throw new AuthorizationError('Authorization 헤더가 없습니다', 401);
}

if (!authorization.startsWith(bearerPrefix)) {
throw new AuthorizationError(`Authorization 헤더는 "${bearerPrefix}"로 시작해야 합니다`, 401);
}

return authorization.slice(bearerPrefix.length);
}

2단계: Logto 테넌트 정보 가져오기

Logto가 발급한 토큰을 검증하려면 다음 값이 필요합니다:

  • JSON Web Key Set (JWKS) URI: JWT 서명 검증에 사용되는 Logto 공개키의 URL입니다.
  • 발급자 (Issuer): 기대하는 발급자 값 (Logto의 OIDC URL).

먼저, Logto 테넌트의 엔드포인트를 찾으세요. 여러 위치에서 확인할 수 있습니다:

  • Logto 콘솔의 설정도메인에서 확인
  • Logto에서 구성한 애플리케이션 설정의 설정엔드포인트 & 자격 증명에서 확인

OpenID Connect 디스커버리 엔드포인트에서 가져오기

이 값들은 Logto의 OpenID Connect 디스커버리 엔드포인트에서 가져올 수 있습니다:

https://<your-logto-endpoint>/oidc/.well-known/openid-configuration

예시 응답 (다른 필드는 생략):

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Logto는 JWKS URI 또는 발급자 (Issuer) 커스터마이징을 허용하지 않으므로, 이 값을 코드에 하드코딩할 수 있습니다. 하지만, 향후 설정이 변경될 경우 유지보수 부담이 커질 수 있으므로 프로덕션 애플리케이션에서는 권장하지 않습니다.

  • JWKS URI: https://<your-logto-endpoint>/oidc/jwks
  • 발급자 (Issuer): https://<your-logto-endpoint>/oidc

3단계: 토큰 및 권한 검증

토큰을 추출하고 OIDC 구성을 가져온 후, 다음을 검증하세요:

  • 서명: JWT가 유효하며 Logto(JWKS를 통해)에서 서명되었는지 확인
  • 발급자 (Issuer): Logto 테넌트의 발급자와 일치해야 함
  • 대상 (Audience): Logto에 등록된 API의 리소스 지표 (Resource indicator) 또는 해당되는 경우 조직 컨텍스트와 일치해야 함
  • 만료: 토큰이 만료되지 않았는지 확인
  • 권한 (스코프): 토큰에 API / 액션에 필요한 스코프가 포함되어야 함. 스코프는 scope 클레임에 공백으로 구분된 문자열로 포함됨
  • 조직 컨텍스트: 조직 수준 API 리소스를 보호하는 경우 organization_id 클레임을 검증

JWT 구조와 클레임에 대해 더 알고 싶다면 JSON Web Token 을 참고하세요.

각 권한 모델별로 확인해야 할 사항

클레임 및 검증 규칙은 권한 모델에 따라 다릅니다:

권한 모델Audience 클레임 (aud)조직 클레임 (organization_id)확인할 스코프 (권한) (scope)
글로벌 API 리소스API 리소스 지표 (Resource indicator)없음API 리소스 권한
조직 (비 API) 권한urn:logto:organization:<id> (조직 컨텍스트가 aud 클레임에 있음)없음조직 권한
조직 수준 API 리소스API 리소스 지표 (Resource indicator)조직 ID (요청과 일치해야 함)API 리소스 권한

비 API 조직 권한의 경우, 조직 컨텍스트는 aud 클레임(예: urn:logto:organization:abc123)으로 표현됩니다. organization_id 클레임은 조직 수준 API 리소스 토큰에만 존재합니다.

:

보안이 중요한 멀티 테넌트 API에서는 항상 권한(스코프)과 컨텍스트(대상, 조직)를 모두 검증하세요.

검증 로직 추가

이 예제에서는 JWT를 검증하기 위해 jose 를 사용합니다. 아직 설치하지 않았다면 설치하세요:

npm install jose

또는 선호하는 패키지 매니저(예: pnpm 또는 yarn)를 사용하세요.

먼저, JWT 검증을 처리하기 위한 공통 유틸리티를 추가하세요:

jwt-validator.ts
import { createRemoteJWKSet, jwtVerify, JWTPayload } from 'jose';
import { AuthInfo, AuthorizationError } from './auth-middleware.js';

const jwks = createRemoteJWKSet(new URL(JWKS_URI));

export async function validateJwtToken(token: string): Promise<JWTPayload> {
const { payload } = await jwtVerify(token, jwks, {
issuer: ISSUER,
});

verifyPayload(payload);
return payload;
}

export function createAuthInfo(payload: JWTPayload): AuthInfo {
const scopes = (payload.scope as string)?.split(' ') ?? [];
const audience = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];

return new AuthInfo(
payload.sub!,
payload.client_id as string,
payload.organization_id as string,
scopes,
audience
);
}

function verifyPayload(payload: JWTPayload): void {
// 권한 모델에 따라 검증 로직을 여기에 구현하세요
// 아래 권한 모델 섹션에서 예시를 확인할 수 있습니다
}

그 다음, 액세스 토큰 (Access token) 을 검증하는 미들웨어를 구현하세요:

auth-middleware.ts
import { Request, Response, NextFunction } from 'express';
import { extractBearerTokenFromHeaders, AuthorizationError } from './auth-middleware.js';
import { validateJwtToken, createAuthInfo } from './jwt-validator.js';

// Express Request 인터페이스에 auth를 확장합니다
declare global {
namespace Express {
interface Request {
auth?: AuthInfo;
}
}
}

export async function verifyAccessToken(req: Request, res: Response, next: NextFunction) {
try {
const token = extractBearerTokenFromHeaders(req.headers);
const payload = await validateJwtToken(token);

// 인증 정보를 request에 저장하여 범용적으로 사용
req.auth = createAuthInfo(payload);

next();
} catch (err: any) {
return res.status(err.status ?? 401).json({ error: err.message });
}
}

권한 모델에 따라 jwt-validator.ts에서 적절한 검증 로직을 구현하세요:

jwt-validator.ts
function verifyPayload(payload: JWTPayload): void {
// audience 클레임이 API 리소스 지표와 일치하는지 확인
const audiences = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
if (!audiences.includes('https://your-api-resource-indicator')) {
throw new AuthorizationError('Invalid audience');
}

// 글로벌 API 리소스에 필요한 스코프 확인
const requiredScopes = ['api:read', 'api:write']; // 실제 필요한 스코프로 교체하세요
const scopes = (payload.scope as string)?.split(' ') ?? [];
if (!requiredScopes.every((scope) => scopes.includes(scope))) {
throw new AuthorizationError('Insufficient scope');
}
}

4단계: 미들웨어를 API에 적용

보호하려는 API 라우트에 미들웨어를 적용하세요.

app.ts
import express from 'express';
import { verifyAccessToken } from './auth-middleware.js';

const app = express();

app.get('/api/protected', verifyAccessToken, (req, res) => {
// req.auth에서 인증 (Authentication) 정보를 직접 접근하세요
res.json({ auth: req.auth });
});

app.get('/api/protected/detailed', verifyAccessToken, (req, res) => {
// 보호된 엔드포인트 로직을 작성하세요
res.json({
auth: req.auth,
message: '보호된 데이터에 성공적으로 접근했습니다',
});
});

app.listen(3000);

5단계: 구현 테스트

유효한 토큰과 유효하지 않은 토큰으로 API를 테스트하여 다음을 확인하세요:

  • 유효한 토큰은 정상적으로 통과하여 접근이 허용됩니다.
  • 유효하지 않거나 누락된 토큰에는 401 Unauthorized를 반환합니다. 필요한 권한 또는 컨텍스트가 없는 유효한 토큰에는 403 Forbidden을 반환합니다.
토큰 클레임 커스터마이징 JSON Web Token (JWT)

OpenID Connect Discovery

RFC 8707: 리소스 지표 (Resource Indicators)