snow · 2026.5.28 03:31 · 조회 2

Logto 인가(Authorization) 설정

LogtoRBACIAM인가API리소스

인증(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 resourcesCreate 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 → RolesCreate role

예시 역할 구성:

역할 이름포함 Scope설명
viewerread:posts읽기 전용
editorread:posts, write:posts작성 가능
adminread: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

댓글

아직 댓글이 없습니다.

댓글을 작성하려면 로그인이 필요합니다.