CAKE20

AI Web PaaS

인증과 권한

로그인이 있는 웹사이트는 반드시 내장 auth로 세션, 쿠키와 권한을 처리합니다.

로그인

  • 로그인 또는 보호 페이지가 있는 웹사이트는 반드시 Cake20의 auth.login, auth.user, auth.require와 auth.logout만 사용합니다.
  • 공통 1차 접근은 package.json auth에 페이지와 /api 경로를 함께 선언해 프런트 이동과 서버 요청에 적용하며 login.hook.ts를 따로 만들지 않습니다.
  • 로그인 화면은 독립된 /login 페이지와 auth 레이아웃으로 구성합니다.
  • 자체 JWT, 세션 쿠키, 브라우저 토큰이나 localStorage·store 기반 인증을 구현하지 않습니다.
  • auth.login(user, maxAge?)의 user에는 id가 필수이고 역할 검사가 필요하면 roles 문자열 배열을 넣습니다.
  • 반환된 cookie를 응답의 set-cookie header에 넣어야 브라우저 로그인이 완료됩니다.
  • maxAge는 선택적인 초 단위 수명이며 웹사이트에 설정된 최대 세션 시간을 넘을 수 없습니다.
  • 비밀번호 원문, API Secret과 불필요한 개인정보는 세션 user에 넣지 않습니다.
  • auth가 반환하는 token을 JSON이나 localStorage로 직접 전달하지 않습니다.
// server/api/login.post.ts
export const config = {
  filter: {
    email: z.string().email(),
    password: z.string().min(8)
  }
};

export default async (data: Input<typeof config.filter>) => {
  // verifyPassword는 웹사이트에서 구현한 비밀번호 해시 검증 함수입니다.
  const user = await db.user.findUnique({
    where: { email: data.email }
  });
  if (!user || !await verifyPassword(data.password, user.password)) {
    throw Object.assign(new Error("로그인 정보가 올바르지 않습니다."), {
      statusCode: 401
    });
  }

  const login = await auth.login({
    id: user.id,
    roles: [user.role],
    name: user.name
  });
  return Response.json({
    user: { id: user.id, name: user.name, roles: [user.role] }
  }, {
    headers: { "set-cookie": login.cookie }
  });
};

Google, GitHub, 네이버 로그인

  • 웹사이트 설정의 소셜 로그인 탭에서 공급자 Client ID와 Client Secret을 입력하고 표시된 콜백 URL을 공급자 앱에 등록합니다.
  • 템플릿 목록의 소셜 로그인 웹사이트를 복제하면 Google, GitHub와 네이버 버튼, 로그인 API와 세션 처리 예제를 함께 시작할 수 있습니다.
  • 로그인 버튼은 /api/auth/google, /api/auth/github 또는 /api/auth/naver로 이동하면 됩니다.
  • auth.oauth는 인증 URL, state, PKCE, 서버 토큰 교환과 HttpOnly 세션 생성을 처리합니다.
  • login(profile)은 공급자와 무관한 OAuthProfile 신호를 받고 웹사이트 세션에 저장할 사용자를 반환합니다.
  • OAuthProfile에는 provider, id와 선택적인 email, emailVerified, name, username, avatar가 들어갑니다.
  • 사용자 계정은 이메일만으로 합치지 말고 provider와 id 조합을 고유하게 저장합니다.
  • auth.providers에는 Client ID와 Secret이 모두 설정된 공급자만 들어갑니다.
  • next, redirect와 error는 현재 웹사이트의 /로 시작하는 경로만 허용합니다.
  • Cake20은 로그인용 접근 토큰을 브라우저나 세션에 저장하지 않습니다.
// server/api/auth/[provider].get.ts
const providers = new Set<OAuthProvider>(["google", "github", "naver"]);

export default async (_input: unknown, context: ApiContext) => {
  const provider = context.params.provider as OAuthProvider;
  if (!providers.has(provider)) {
    throw Object.assign(new Error("지원하지 않는 로그인 공급자입니다."), {
      statusCode: 404
    });
  }
  return auth.oauth(context.request, {
    provider,
    redirect: "/account",
    error: "/login",
    async login(profile) {
      const user = await findOrCreateUser(profile);
      return { id: user.id, roles: [user.role], name: user.name };
    }
  });
};

현재 사용자와 권한

  • auth.user(request)는 현재 세션 사용자를 반환하고 로그인하지 않았으면 null을 반환합니다.
  • auth.require(request)는 로그인하지 않은 요청에 401 오류를 발생시킵니다.
  • auth.require(request, roles)는 사용자 roles에 전달한 역할 중 하나도 없으면 403 오류를 발생시킵니다.
  • 반환 타입을 웹사이트 사용자 타입으로 지정하려면 auth.user<MyUser>()와 auth.require<MyUser>()를 사용합니다.
  • 브라우저는 같은 origin의 상대 API 요청에 세션 쿠키를 자동 전송하므로 별도 auth header가 필요하지 않습니다.
  • 에디터 디자인 모드는 웹사이트 Bun 서버를 실행하지 않습니다. 각 *.db.ts seed의 현재 반환 row로 만든 app/preview JSON을 사용합니다.
  • 보호 페이지를 직접 선택한 디자인 Preview에서는 웹사이트 코드가 /login으로 이동시키더라도 선택한 화면을 유지합니다.
  • /login, /signin, /sign-in, /auth 또는 /로그인 페이지를 직접 선택하면 GET API가 401을 반환해 로그인 화면 자체를 표시합니다.
  • 디자인 모드의 POST, PUT, PATCH, DELETE API 요청은 차단되며 실제 인증과 데이터 검증은 런타임 모드에서 수행합니다.
// server/api/me.get.ts
export default async (_input: unknown, context: ApiContext) => ({
  user: await auth.user(context.request)
});

// server/api/admin.get.ts
export default async (_input: unknown, context: ApiContext) => {
  const user = await auth.require(context.request, ["admin", "owner"]);
  return { user, report: await createReport() };
};

로그아웃

  • auth.logout(request)은 서버의 현재 세션을 삭제합니다.
  • 반환된 cookie를 set-cookie에 넣어 브라우저의 쿠키도 함께 제거합니다.
  • 로그아웃 이후 보호 API는 auth.require에서 401을 반환합니다.
// server/api/logout.post.ts
export default async (_input: unknown, context: ApiContext) => {
  const logout = await auth.logout(context.request);
  return Response.json({ ok: true }, {
    headers: { "set-cookie": logout.cookie }
  });
};

저장 위치와 보안

  • 브라우저에는 무작위 세션 식별자만 HttpOnly, SameSite=Lax 쿠키로 저장하며 운영 HTTPS에서는 Secure도 적용합니다.
  • 사용자 세션 데이터는 웹사이트별 Redis에 저장합니다.
  • 세션 데이터는 최대 64KB이며 웹사이트 설정의 수명 안에서 유지됩니다.
  • 세션은 웹사이트 설정의 시간만큼 유지되며 30분 동안 요청이 없으면 먼저 만료됩니다.
  • auth는 Request를 받는 API, Route와 Event 같은 서버 코드용이고 store는 Cake20 View 브라우저 상태용입니다.
  • 로그인 여부가 필요하면 /api/me처럼 auth.user 결과를 반환하는 API를 호출합니다. store에는 공개 가능한 사용자 표시 정보만 선택적으로 보관합니다.