인증

이 페이지

EmDash는 패스키 인증을 기본 로그인 방식으로 사용합니다. 패스키는 피싱에 강하고, 비밀번호가 필요 없으며, 브라우저나 비밀번호 관리자를 통해 기기 간에 작동합니다.

Cloudflare 배포의 경우 선택적으로 Cloudflare Access를 대체 인증 제공자로 사용할 수 있습니다.

동작 방식

패스키는 WebAuthn을 사용합니다. 기기에 저장되거나 비밀번호 관리자를 통해 동기화되는 공개 키 자격 증명을 생성하는 웹 표준입니다. 로그인 시 기기가 자격 증명의 소유를 증명하며, 비밀번호가 네트워크를 통해 전송되지 않습니다.

패스키 인증의 장점:

  • 기억하거나 유출될 비밀번호가 없음
  • 피싱 방지 — 자격 증명이 사이트 도메인에 바인딩
  • 기기 간 동기화 — iCloud 키체인, Google 비밀번호 관리자, 1Password 등과 호환
  • 빠른 로그인 — 생체 인식 또는 PIN으로 한 번 탭

첫 사용자 설정

관리 패널에 처음 접근하면 설정 마법사가 관리자 계정 생성을 안내합니다.

  1. http://localhost:4321/_emdash/admin으로 이동합니다

  2. 설정 마법사로 리디렉션됩니다. 다음을 입력합니다:

    • Site Title — 사이트 이름
    • Tagline — 짧은 설명
    • Admin Email — 이메일 주소
  3. Create Site를 클릭해 패스키를 등록합니다

  4. 브라우저가 패스키 생성을 요청합니다:

    • macOS: Touch ID, 기기 비밀번호 또는 보안 키
    • Windows: Windows Hello 또는 보안 키
    • 모바일: Face ID, 지문 또는 PIN
  5. 패스키가 등록되면 로그인되어 관리 대시보드로 이동합니다.

로그인

설정 후 관리 패널에 돌아오면 패스키 인증이 실행됩니다:

  1. /_emdash/admin을 방문합니다

  2. 로그인하지 않은 상태라면 로그인 페이지가 표시됩니다

  3. Sign in을 클릭해 인증합니다

  4. 브라우저가 패스키를 요청합니다(생체 인식, PIN 또는 보안 키)

  5. 확인 후 관리 대시보드로 리디렉션됩니다

매직 링크 대체

패스키를 사용할 수 없는 경우(예: 기기 분실) 매직 링크를 대안으로 사용합니다. 이메일 구성이 필요합니다.

  1. 로그인 페이지에서 Sign in with email을 클릭합니다

  2. 이메일 주소를 입력합니다

  3. 받은 편지함에서 로그인 링크를 확인합니다

  4. 링크를 클릭해 인증합니다(15분간 유효)

OAuth 로그인

EmDash는 구성 시 GitHub 및 Google을 통한 OAuth 로그인을 지원합니다. 사용자는 최초 패스키 설정 후 계정을 연결할 수 있습니다.

설정 방법은 구성 가이드를 참고하세요.

사용자 역할

EmDash는 5단계 역할 기반 접근 제어를 사용합니다:

역할레벨설명
Subscriber10보기 전용
Contributor20콘텐츠 작성(승인 필요)
Author30자기 콘텐츠 작성/편집/게시
Editor40모든 콘텐츠 관리
Admin50설정 포함 전체 접근

각 역할은 하위 레벨의 모든 권한을 상속합니다. 첫 사용자는 항상 Admin으로 생성됩니다.

사용자 초대

관리자는 관리 패널에서 새 사용자를 초대할 수 있습니다:

  1. Settings > Users로 이동합니다

  2. Invite User를 클릭합니다

  3. 사용자 이메일과 역할을 입력합니다

  4. Send Invite를 클릭합니다

  5. 사용자에게 초대 링크가 포함된 이메일이 발송됩니다

  6. 링크를 클릭하고 패스키를 등록합니다

초대는 7일간 유효합니다. 관리자는 Users 페이지에서 초대를 재발송하거나 취소할 수 있습니다.

패스키 관리

사용자는 계정 설정에서 패스키를 관리할 수 있습니다:

  • 패스키 추가 — 백업용이나 다른 기기에 추가 패스키 등록
  • 패스키 삭제 — 더 이상 사용하지 않는 패스키 제거
  • 패스키 이름 변경 — 패스키에 설명 이름 부여

각 사용자는 최대 10개의 패스키를 등록할 수 있습니다.

셀프 회원가입

팀 사이트의 경우 특정 이메일 도메인에 대해 셀프 회원가입을 활성화할 수 있습니다:

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";

export default defineConfig({
	integrations: [
		emdash({
			auth: {
				selfSignup: {
					domains: ["example.com"],
					defaultRole: "contributor",
				},
			},
		}),
	],
});

일치하는 이메일 도메인의 사용자는 초대 없이 가입할 수 있습니다. 확인 이메일을 받고 패스키를 등록해 가입을 완료합니다.

세션 구성

세션은 합리적인 기본값으로 안전한 HttpOnly 쿠키를 사용합니다:

emdash({
	auth: {
		session: {
			maxAge: 30 * 24 * 60 * 60, // 30일(기본값)
			sliding: true, // 활동 시 만료 재설정
		},
	},
});

보안 참고사항

  • 패스키는 공개 키로 저장 — 개인 키는 기기를 벗어나지 않음
  • 챌린지 검증으로 재전송 공격 방지
  • 속도 제한으로 무차별 대입 방지(IP당 분당 5회)
  • 세션은 HttpOnly, Secure, SameSite=Lax 쿠키 보안
  • 매직 링크 토큰은 SHA-256 해시 — 원시 토큰은 저장하지 않음

문제 해결

”No passkeys registered”

로그인 시 이 오류가 나타나면 패스키가 비밀번호 관리자에서 삭제되었을 수 있습니다. 관리자에게 매직 링크나 새 초대를 요청하세요.

”Passkey authentication failed”

보통 다른 도메인용으로 만든 패스키입니다. 패스키는 도메인에 바인딩되어 localhost:4321용 패스키는 example.com에서 작동하지 않습니다. 각 도메인에 새 패스키를 등록하세요.

”Session expired”

세션은 기본 30일이며 슬라이딩 만료입니다. 예기치 않게 로그아웃되면 쿠키를 지우고 다시 로그인하세요.

모든 패스키 분실

등록된 모든 패스키에 접근할 수 없는 경우:

  1. 다른 관리자에게 매직 링크를 요청합니다(이메일 구성 필요)
  2. 매직 링크로 로그인합니다
  3. 계정 설정에서 새 패스키를 등록합니다

유일한 관리자이고 이메일이 구성되지 않은 경우 데이터베이스를 통해 인증을 재설정해야 합니다.

Cloudflare Access

Cloudflare에 배포할 때 패스키 대신 Cloudflare Access를 인증 제공자로 사용할 수 있습니다. Access는 기존 ID 제공자를 사용해 엣지에서 인증을 처리합니다.

Cloudflare Access를 사용하는 이유

  • 싱글 사인온 — 회사 IdP로 사용자 인증
  • 중앙 집중 접근 제어 — Cloudflare 대시보드에서 관리자 접근 관리
  • 패스키 관리 불필요 — 패스키를 등록하거나 관리할 필요 없음
  • 그룹 기반 역할 — IdP 그룹을 EmDash 역할에 자동 매핑

설정

  1. EmDash 사이트용 Cloudflare Access 애플리케이션을 생성합니다
  2. 애플리케이션 설정에서 Application Audience (AUD) Tag를 확인합니다
  3. EmDash에서 Access를 구성합니다:
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import emdash from "emdash/astro";
import { d1, access } from "@emdash-cms/cloudflare";

export default defineConfig({
	output: "server",
	adapter: cloudflare(),
	integrations: [
		emdash({
			database: d1({ binding: "DB" }),
			auth: access({
				teamDomain: "myteam.cloudflareaccess.com",
				audience: "abc123def456...", // Access 앱 설정에서
			}),
		}),
	],
});

구성 옵션

옵션타입기본값설명
teamDomainstring필수Access 팀 도메인 (예: myteam.cloudflareaccess.com)
audiencestring필수Access 설정의 Application Audience (AUD) 태그
autoProvisionbooleantrue첫 Access 로그인 시 EmDash 사용자 자동 생성
defaultRolenumber30그룹에 매칭되지 않는 사용자의 역할 (30 = Author)
syncRolesbooleanfalse로그인마다 IdP 그룹 기반으로 역할 업데이트
roleMappingobjectIdP 그룹 이름을 역할 레벨에 매핑
audienceEnvVarstring"CF_ACCESS_AUDIENCE"audience 태그의 환경 변수 이름(하드코딩 대안)

역할 매핑

IdP 그룹을 EmDash 역할에 매핑합니다:

emdash({
	auth: access({
		teamDomain: "myteam.cloudflareaccess.com",
		audience: "abc123...",
		roleMapping: {
			Admins: 50, // Admin
			"Content Editors": 40, // Editor
			Writers: 30, // Author
		},
		defaultRole: 20, // 그룹에 없는 사용자는 Contributor
	}),
});

사용자가 여러 그룹에 속한 경우 첫 번째 일치하는 그룹이 적용됩니다. 첫 번째 사용자는 그룹과 관계없이 항상 Admin이 됩니다.

역할 동기화 동작

기본값(syncRoles: false)에서는 사용자 역할이 첫 로그인 시 설정되고 이후 변경되지 않습니다. 관리자가 EmDash에서 역할을 수동 조정할 수 있습니다.

IdP 그룹을 권위 소스로 사용하려면 syncRoles: true를 설정하세요 — 매 로그인마다 현재 그룹을 기반으로 역할이 업데이트됩니다.

동작 방식

  1. 사용자가 /_emdash/admin을 방문합니다
  2. Cloudflare Access가 가로채서 IdP로 리디렉션합니다
  3. 사용자가 인증합니다(SSO, MFA 등)
  4. Access가 요청에 서명된 JWT를 설정합니다
  5. EmDash가 JWT를 검증하고 사용자를 생성/인증합니다

비활성화되는 기능

Access가 활성화되면 다음 기능을 사용할 수 없습니다:

  • 로그인 페이지 (/_emdash/admin/login)
  • 패스키 등록 및 관리
  • OAuth 로그인
  • 매직 링크 로그인
  • 셀프 회원가입
  • 사용자 초대

사용자 관리는 Cloudflare Access 정책으로 전적으로 수행됩니다.

문제 해결

”No Access JWT present”

요청이 Access JWT 없이 EmDash에 도달했습니다. 원인:

  • Access가 애플리케이션을 보호하도록 구성되지 않음
  • Access 정책이 관리 라우트와 일치하지 않음

Access 애플리케이션이 /_emdash/admin/*을 포함하는지 확인하세요.

”JWT audience mismatch”

구성의 audience가 JWT와 일치하지 않습니다. Access 애플리케이션 설정에서 Application Audience Tag를 다시 확인하세요.

”User not authorized”

사용자가 Access를 통해 인증되었지만 autoProvisionfalse이고 EmDash에 존재하지 않습니다. 다음 중 하나를 수행하세요:

  • autoProvision: true로 설정하거나
  • 로그인 전에 사용자를 수동 생성