본문으로 건너뛰기

Capacitor JS 애플리케이션에 인증 (Authentication)을 추가하세요

팁:
  • 다음 데모는 Capacitor JS 5.0.6을 기반으로 구축되었습니다.

사전 준비 사항​

설치​

선호하는 패키지 관리자를 통해 Logto SDK 및 피어 종속성을 설치하세요:

npm i @logto/capacitor
npm i @capacitor/browser @capacitor/app @capacitor/preferences

@logto/capacitor 패키지는 Logto의 SDK입니다. 나머지 패키지들은 그 피어 종속성입니다.

통합​

Logto 클라이언트 초기화​

다음 코드를 Capacitor 프로젝트에 추가하세요:

import LogtoClient, { type LogtoConfig } from '@logto/capacitor';

const logtoConfig: LogtoConfig = {
endpoint: '<your-logto-endpoint>',
appId: '<your-application-id>',
};

const logtoClient = new LogtoClient(config);

로그인 구현​

자세한 내용을 살펴보기 전에, 최종 사용자 경험에 대한 간단한 개요를 소개합니다. 로그인 과정은 다음과 같이 단순화할 수 있습니다:

  1. 귀하의 앱이 로그인 메서드를 호출합니다.
  2. 사용자는 Logto 로그인 페이지로 리디렉션됩니다. 네이티브 앱의 경우, 시스템 브라우저가 열립니다.
  3. 사용자가 로그인하면, 다시 귀하의 앱(리디렉션 URI로 설정됨)으로 리디렉션됩니다.

리디렉션 기반 로그인에 관하여​

  1. 이 인증 과정은 OpenID Connect (OIDC) 프로토콜을 따르며, Logto는 사용자 로그인을 보호하기 위해 엄격한 보안 조치를 시행합니다.
  2. 여러 앱이 있는 경우, 동일한 아이덴티티 제공자 (Logto)를 사용할 수 있습니다. 사용자가 한 앱에 로그인하면, Logto는 사용자가 다른 앱에 접근할 때 자동으로 로그인 과정을 완료합니다.

리디렉션 기반 로그인에 대한 이론적 배경과 이점에 대해 더 알고 싶다면, Logto 로그인 경험 설명을 참조하세요.


이제 리디렉션 URI를 구성해 보겠습니다. 리디렉션 URI는 인증 흐름 후 사용자를 애플리케이션으로 다시 리디렉션하는 데 사용됩니다.

URI가 Capacitor 앱으로 리디렉션되도록 하세요. 예를 들어, com.example.app://callback. 값은 Capacitor 앱 구성에 따라 다를 수 있습니다. 자세한 내용은 Capacitor Deep Links를 참조하세요.

그런 다음, 로그인 버튼의 onClick 핸들러에 다음 코드를 추가하세요:

const onClick = async () => {
await logtoClient.signIn('com.example.app://callback');
console.log(await logtoClient.isAuthenticated()); // true
console.log(await logtoClient.getIdTokenClaims()); // { sub: '...', ... }
};

로그아웃 구현​

Capacitor는 iOS에서 Safari View Controller와 Android에서 Chrome Custom Tabs를 활용하기 때문에 인증 상태가 잠시 동안 유지될 수 있습니다. 그러나 때때로 사용자는 애플리케이션에서 즉시 로그아웃하고 싶을 수 있습니다. 이 경우, signOut 메서드를 사용하여 사용자를 로그아웃할 수 있습니다:

const onClick = async () => {
await logtoClient.signOut();
console.log(await logtoClient.isAuthenticated()); // false
};

signOut 메서드는 로그아웃 후 리디렉션 URI에 대한 선택적 매개변수를 가지고 있습니다. 제공되지 않으면 사용자는 Logto 로그아웃 페이지로 리디렉션됩니다.

사용자는 웹 뷰를 닫고 Capacitor 앱으로 돌아가기 위해 "완료"를 클릭해야 합니다. 사용자를 자동으로 Capacitor 앱으로 다시 리디렉션하고 싶다면, 로그아웃 후 리디렉션 URI를 제공할 수 있습니다. 예를 들어, com.example.app://callback/sign-out.

로그아웃 후 리디렉션 URI가 Capacitor 앱으로 리디렉션될 수 있도록 하세요. 그런 다음 로그아웃 버튼의 onClick 핸들러에 다음 코드를 추가하세요:

const onClick = async () => {
await logtoClient.signOut('com.example.app://callback/sign-out');
};

체크포인트: 인증 흐름 트리거​

Capacitor 앱을 실행하고 로그인 버튼을 클릭하세요. 브라우저 창이 열리며 Logto 로그인 페이지로 리디렉션됩니다.

사용자가 인증 흐름을 완료하지 않고 브라우저 창을 닫으면, Capacitor 앱은 LogtoClientError를 수신하게 됩니다.

사용자 정보 가져오기​

사용자 정보 표시​

사용자의 정보를 표시하려면 getIdTokenClaims() 메서드를 사용할 수 있습니다. 예를 들어, Capacitor 앱에서:

const userClaims = await logtoClient.getIdTokenClaims();
console.log(userClaims);

추가 클레임 요청​

client.getIdTokenClaims()에서 반환된 객체에 일부 사용자 정보가 누락된 것을 발견할 수 있습니다. 이는 OAuth 2.0 및 OpenID Connect (OIDC)가 최소 권한 원칙 (PoLP)을 따르도록 설계되었기 때문이며, Logto는 이러한 표준을 기반으로 구축되었습니다.

기본적으로 제한된 클레임 (Claim)만 반환됩니다. 더 많은 정보를 원하시면, 추가적인 스코프 (Scope)를 요청하여 더 많은 클레임에 접근할 수 있습니다.

정보:

"클레임 (Claim)"은 주체에 대해 주장하는 내용이며, "스코프 (Scope)"는 클레임의 그룹입니다. 현재의 경우, 클레임은 사용자에 대한 정보입니다.

다음은 스코프 - 클레임 관계의 비규범적 예시입니다:

팁:

"sub" 클레임은 "주체"를 의미하며, 이는 사용자의 고유 식별자 (즉, 사용자 ID)입니다.

Logto SDK는 항상 세 가지 스코프를 요청합니다: openid, profile, 그리고 offline_access.

추가 스코프를 요청하려면 클라이언트를 초기화할 때 LogtoConfig 객체에 스코프를 전달할 수 있습니다. 예를 들어:

const logtoConfig = {
scopes: ['email', 'phone', 'custom_data', 'organizations'],
};

그런 다음 client.getIdTokenClaims()의 반환 값에서 추가 클레임에 접근할 수 있습니다:

네트워크 요청이 필요한 클레임​

ID 토큰의 비대화를 방지하기 위해, 일부 클레임은 네트워크 요청을 통해 가져와야 합니다. 예를 들어, custom_data 클레임은 스코프에서 요청되더라도 사용자 객체에 포함되지 않습니다. 이러한 클레임에 접근하려면, client.fetchUserInfo() 메서드를 사용할 수 있습니다:

const userInfo = await logtoClient.fetchUserInfo();
console.log(userInfo);
이 메서드는 userinfo 엔드포인트에 요청하여 사용자 정보를 가져옵니다. 사용 가능한 스코프와 클레임에 대해 더 알고 싶다면, 스코프와 클레임 섹션을 참조하세요.

스코프와 클레임​

Logto는 OIDC 스코프 (Scope) 및 클레임 (Claim) 규칙을 사용하여 ID 토큰 및 OIDC userinfo 엔드포인트에서 사용자 정보를 가져오기 위한 스코프 (Scope)와 클레임 (Claim)을 정의합니다. "스코프 (Scope)"와 "클레임 (Claim)" 모두 OAuth 2.0 및 OpenID Connect (OIDC) 명세에서 온 용어입니다.

표준 OIDC 클레임 (Claim)의 경우, ID 토큰에 포함되는지는 요청된 스코프 (Scope)에 의해 엄격하게 결정됩니다. 확장 클레임 (예: custom_data 및 organizations)은 커스텀 ID 토큰 설정을 통해 ID 토큰에 추가로 포함되도록 구성할 수 있습니다.

요약하면, 스코프 (Scope)를 요청하면 해당하는 클레임 (Claim)을 사용자 정보에서 받을 수 있습니다. 예를 들어, `email` 스코프 (Scope)를 요청하면 사용자의 `email` 및 `email_verified` 데이터를 받게 됩니다.

기본적으로 Logto SDK는 항상 세 가지 스코프 (Scope)를 요청합니다: `openid`, `profile`, 그리고 `offline_access`입니다. 이 기본 스코프 (Scope)는 제거할 수 없지만, Logto를 구성할 때 더 많은 스코프 (Scope)를 추가할 수 있습니다:

import { type LogtoConfig, UserScope } from '@logto/capacitor';

const config: LogtoConfig = {
// ...other options
scopes: [UserScope.Email, UserScope.Phone], // 필요한 스코프를 추가하세요
};

지원되는 스코프와 해당 클레임(Claim)의 목록은 다음과 같습니다:

표준 OIDC 스코프​

openid (기본값)

클레임(Claim) 이름타입설명
substring사용자의 고유 식별자

profile (기본값)

클레임(Claim) 이름타입설명
namestring사용자의 전체 이름
usernamestring사용자의 사용자명
picturestring최종 사용자의 프로필 사진 URL. 이 URL은 이미지 파일(예: PNG, JPEG, GIF 이미지 파일)을 가리켜야 하며, 이미지를 포함한 웹 페이지가 아니어야 합니다. 이 URL은 최종 사용자를 설명할 때 표시하기에 적합한 프로필 사진을 명확히 참조해야 하며, 최종 사용자가 임의로 촬영한 사진이 아니어야 합니다.
created_atnumber최종 사용자가 생성된 시간. 시간은 Unix epoch (1970-01-01T00:00:00Z) 이후 밀리초로 표시됩니다.
updated_atnumber최종 사용자의 정보가 마지막으로 업데이트된 시간. 시간은 Unix epoch (1970-01-01T00:00:00Z) 이후 밀리초로 표시됩니다.

기타 표준 클레임(Claim)에는 family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo, locale 등이 있으며, 이들은 userinfo 엔드포인트를 요청하지 않아도 profile 스코프에 포함됩니다. 위의 클레임과의 차이점은, 이 클레임들은 값이 비어 있지 않을 때만 반환되며, 위의 클레임들은 값이 비어 있으면 null을 반환합니다.

노트:

표준 클레임(Claim)과 달리, created_at 및 updated_at 클레임은 초 단위가 아닌 밀리초 단위를 사용합니다.

email

클레임(Claim) 이름타입설명
emailstring사용자의 이메일 주소
email_verifiedboolean이메일 주소가 인증되었는지 여부

phone

클레임(Claim) 이름타입설명
phone_numberstring사용자의 전화번호
phone_number_verifiedboolean전화번호가 인증되었는지 여부

address

주소 클레임(Claim)의 세부 사항은 OpenID Connect Core 1.0 을 참조하세요.

정보:

**(기본값)**으로 표시된 스코프는 항상 Logto SDK에서 요청합니다. 표준 OIDC 스코프의 클레임(Claim)은 해당 스코프가 요청될 때 항상 ID 토큰 (ID token)에 포함되며, 비활성화할 수 없습니다.

확장 스코프​

다음 스코프는 Logto에서 확장한 것으로, userinfo 엔드포인트를 통해 클레임(Claim)을 반환합니다. 이 클레임들은 Console > Custom JWT를 통해 ID 토큰 (ID token)에 직접 포함되도록 설정할 수도 있습니다. 자세한 내용은 커스텀 ID 토큰을 참고하세요.

custom_data

클레임(Claim) 이름타입설명기본적으로 ID 토큰에 포함됨
custom_dataobject사용자의 커스텀 데이터

identities

클레임(Claim) 이름타입설명기본적으로 ID 토큰에 포함됨
identitiesobject사용자의 연결된 아이덴티티
sso_identitiesarray사용자의 연결된 SSO 아이덴티티

roles

클레임(Claim) 이름타입설명기본적으로 ID 토큰에 포함됨
rolesstring[]사용자의 역할 (Role)✅

urn:logto:scope:organizations

클레임(Claim) 이름타입설명기본적으로 ID 토큰에 포함됨
organizationsstring[]사용자가 속한 조직 (Organization) ID✅
organization_dataobject[]사용자가 속한 조직 (Organization) 데이터
노트:

이러한 조직 (Organization) 클레임(Claim)은 불투명 토큰 (Opaque token)을 사용할 때도 userinfo 엔드포인트를 통해 조회할 수 있습니다. 그러나 불투명 토큰 (Opaque token)은 조직 토큰 (Organization token)으로 사용되어 조직별 리소스에 접근할 수 없습니다. 자세한 내용은 불투명 토큰 (Opaque token)과 조직 (Organization)을 참고하세요.

urn:logto:scope:organization_roles

클레임(Claim) 이름타입설명기본적으로 ID 토큰에 포함됨
organization_rolesstring[]사용자가 속한 조직 (Organization)의 역할 (Role), 형식: <organization_id>:<role_name>✅

API 리소스 및 조직​

먼저 🔐 역할 기반 접근 제어 (RBAC)를 읽어 Logto RBAC의 기본 개념과 API 리소스를 적절히 설정하는 방법을 이해하는 것을 권장합니다.

Logto 클라이언트 구성하기​

API 리소스를 설정한 후, 애플리케이션에서 Logto를 구성할 때 이를 추가할 수 있습니다:

import { type LogtoConfig } from '@logto/capacitor';

const config: LogtoConfig = {
appId: '<your-application-id>',
endpoint: '<your-logto-endpoint>',
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // API 리소스를 추가하세요
};

각 API 리소스는 자체 권한 (스코프)을 가지고 있습니다.

예를 들어, https://shopping.your-app.com/api 리소스는 shopping:read 및 shopping:write 권한을 가지고 있으며, https://store.your-app.com/api 리소스는 store:read 및 store:write 권한을 가지고 있습니다.

이러한 권한을 요청하려면, 애플리케이션에서 Logto를 구성할 때 추가할 수 있습니다:

import { type LogtoConfig } from '@logto/capacitor';

const config: LogtoConfig = {
appId: '<your-application-id>',
endpoint: '<your-logto-endpoint>',
scopes: ['shopping:read', 'shopping:write', 'store:read', 'store:write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
};

스코프가 API 리소스와 별도로 정의된 것을 알 수 있습니다. 이는 OAuth 2.0을 위한 리소스 지표가 요청의 최종 스코프가 모든 대상 서비스의 모든 스코프의 데카르트 곱이 될 것이라고 명시하기 때문입니다.

따라서 위의 경우, Logto에서 정의된 스코프를 단순화할 수 있으며, 두 API 리소스 모두 접두사 없이 read 및 write 스코프를 가질 수 있습니다. 그런 다음, Logto 구성에서:

import { type LogtoConfig } from '@logto/capacitor';

const config: LogtoConfig = {
appId: '<your-application-id>',
endpoint: '<your-logto-endpoint>',
scopes: ['read', 'write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'],
};

모든 API 리소스에 대해 read 및 write 스코프를 요청하게 됩니다.

노트:

API 리소스에 정의되지 않은 스코프를 요청해도 괜찮습니다. 예를 들어, API 리소스에 email 스코프가 없더라도 email 스코프를 요청할 수 있습니다. 사용 불가능한 스코프는 안전하게 무시됩니다.

성공적으로 로그인한 후, Logto는 사용자의 역할에 따라 API 리소스에 적절한 스코프를 발급합니다.

API 리소스를 위한 액세스 토큰 가져오기​

특정 API 리소스에 대한 액세스 토큰을 가져오려면 getAccessToken 메서드를 사용할 수 있습니다:

const token = await logtoClient.getAccessToken('https://shopping.your-app.com/api');

이 메서드는 사용자가 관련 권한을 가지고 있을 때 API 리소스에 접근할 수 있는 JWT 액세스 토큰을 반환합니다. 현재 캐시된 액세스 토큰이 만료된 경우, 이 메서드는 자동으로 리프레시 토큰을 사용하여 새로운 액세스 토큰을 얻으려고 시도합니다.

조직 토큰 가져오기​

조직이 처음이라면, 시작하기 위해 🏢 조직 (다중 테넌시)을 읽어보세요.

Logto 클라이언트를 구성할 때 UserScope.Organizations 스코프를 추가해야 합니다:

import { type LogtoConfig, UserScope } from '@logto/capacitor';

const config: LogtoConfig = {
// ...other configs
scopes: [UserScope.Organizations],
};

사용자가 로그인하면, 사용자에 대한 조직 토큰을 가져올 수 있습니다:

await logtoClient.getOrganizationToken(organizationId);

추가 자료​

엔드유저 플로우: 인증 (Authentication) 플로우, 계정 플로우, 조직 플로우 커넥터 구성하기 인가 (Authorization)