snow · 2026.5.28 03:31 · 조회 2
Logto 인가(Authorization) 설정
인증(Authentication)이 "누구인가?"를 확인한다면, 인가(Authorization)는 "무엇을 할 수 있는가?"를 결정합니다. Logto는 OAuth 2.0 기반 API 리소스 보호와 RBAC(Role-Based Access Control)를 지원합니다.
5.1 API 리소스 등록
API 리소스는 보호할 백엔드 API의 식별자(Resource Indicator)입니다. 클라이언트가 Access Token을 요청할 때 어떤 API에 접근할지 명시하는 데 사용됩니다.
API 리소스 생성
Admin Console → API resources → Create API resource
이름: My Backend API
API Identifier (Resource): https://api.yourapp.com
Token expiration: 3600 (초)
API Identifier는 실제 호출 URL일 필요는 없지만, 관례적으로 API 기본 URL을 사용합니다.
API 퍼미션(Scope) 정의
API 리소스를 생성한 후, 해당 API에서 지원하는 권한(Scope)을 정의합니다.
예시 — 블로그 API:
| Scope 이름 | 설명 |
|---|---|
read:posts | 게시글 조회 |
write:posts | 게시글 생성/수정 |
delete:posts | 게시글 삭제 |
admin:all | 모든 관리 권한 |
클라이언트에서 Access Token 요청
import { useLogto } from '@logto/react';
function useApiAccess() {
const { getAccessToken } = useLogto();
const callApi = async (path: string) => {
const token = await getAccessToken('https://api.yourapp.com');
return fetch(`https://api.yourapp.com${path}`, {
headers: { Authorization: `Bearer ${token}` },
});
};
return { callApi };
}
5.2 RBAC 설정
RBAC(역할 기반 접근 제어)는 권한(Scope)을 역할(Role)에 묶고, 역할을 사용자에게 할당하는 방식입니다.
역할(Role) 생성
Admin Console → Roles → Create role
예시 역할 구성:
| 역할 이름 | 포함 Scope | 설명 |
|---|---|---|
viewer | read:posts | 읽기 전용 |
editor | read:posts, write:posts | 작성 가능 |
admin | read:posts, write:posts, delete:posts, admin:all | 전체 권한 |
사용자에게 역할 할당
Admin Console에서 할당:
Users → 사용자 선택 → Roles → Assign roles → viewer / editor / admin 선택
Management API로 할당:
await fetch(
`https://your-tenant-id.logto.app/api/users/${userId}/roles`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${managementToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ roleIds: ['role-id-editor'] }),
}
);
Access Token payload 예시
{
"sub": "user-id",
"iss": "https://your-tenant-id.logto.app/oidc",
"aud": "https://api.yourapp.com",
"scope": "read:posts write:posts",
"exp": 1234567890,
"iat": 1234564290
}
5.3 백엔드 API에서 토큰 검증
Node.js/Express 예시 (jose 라이브러리)
npm install jose
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { Request, Response, NextFunction } from 'express';
const JWKS_URI = 'https://your-tenant-id.logto.app/oidc/jwks';
const ISSUER = 'https://your-tenant-id.logto.app/oidc';
const AUDIENCE = 'https://api.yourapp.com';
const jwks = createRemoteJWKSet(new URL(JWKS_URI));
export async function requireAuth(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
try {
const { payload } = await jwtVerify(authHeader.slice(7), jwks, {
issuer: ISSUER,
audience: AUDIENCE,
});
req.user = {
id: payload.sub!,
scopes: (payload.scope as string ?? '').split(' '),
};
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
}
export function requireScope(scope: string) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user?.scopes.includes(scope)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
라우터에 미들웨어 적용
router.get('/posts', requireAuth, requireScope('read:posts'), async (req, res) => {
res.json(await PostService.findAll());
});
router.post('/posts', requireAuth, requireScope('write:posts'), async (req, res) => {
res.status(201).json(await PostService.create(req.body, req.user!.id));
});
router.delete('/posts/:id', requireAuth, requireScope('delete:posts'), async (req, res) => {
await PostService.delete(req.params.id);
res.status(204).send();
});
Python/FastAPI 예시
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import jwt
import httpx
JWKS_URI = "https://your-tenant-id.logto.app/oidc/jwks"
ISSUER = "https://your-tenant-id.logto.app/oidc"
AUDIENCE = "https://api.yourapp.com"
security = HTTPBearer()
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
async with httpx.AsyncClient() as client:
jwks = (await client.get(JWKS_URI)).json()
try:
return jwt.decode(credentials.credentials, jwks, algorithms=["RS256"],
audience=AUDIENCE, issuer=ISSUER)
except Exception:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
def require_scope(required_scope: str):
async def checker(user = Depends(get_current_user)):
if required_scope not in user.get("scope", "").split():
raise HTTPException(status_code=403, detail="Insufficient scope")
return user
return checker
다음 단계
API 보호와 RBAC 설정이 완료되었습니다. 여러 조직을 지원하는 멀티 테넌트 구조를 알아보십시오.
다음: Logto 멀티 테넌트 (Organizations) — 조직 개념, 멤버 관리, 조직별 RBAC
참고: Logto Authorization — https://docs.logto.io/authorization
댓글
아직 댓글이 없습니다.
댓글을 작성하려면 로그인이 필요합니다.