본문으로 건너뛰기

Chrome 확장 애플리케이션에 인증 (Authentication)을 추가하세요

이 가이드는 Logto를 Chrome 확장에 통합하는 방법을 보여줍니다.

팁:
  • 다음 시연은 Chrome v123.0.6312.87 (arm64)에서 테스트되었습니다. SDK에서 사용된 chrome API를 지원하는 한, 다른 버전에서도 작동해야 합니다.
  • 샘플 프로젝트는 우리의 GitHub 저장소에서 확인할 수 있습니다.

사전 준비 사항​

설치​

npm i @logto/chrome-extension

통합​

인증 흐름​

Chrome 확장의 팝업에 "로그인" 버튼을 넣었다고 가정하면, 인증 흐름은 다음과 같습니다:

확장의 다른 상호작용 페이지에 대해서는 확장 팝업 참가자를 페이지 이름으로 교체하면 됩니다. 이 튜토리얼에서는 팝업 페이지에 집중할 것입니다.

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

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

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

manifest.json 업데이트​

Logto SDK는 manifest.json에서 다음 권한이 필요합니다:

manifest.json
{
"permissions": ["identity", "storage"],
"host_permissions": ["https://*.logto.app/*"]
}
  • permissions.identity: 로그인 및 로그아웃에 사용되는 Chrome Identity API에 필요합니다.
  • permissions.storage: 사용자의 세션을 저장하는 데 필요합니다.
  • host_permissions: Logto SDK가 Logto API와 통신하는 데 필요합니다.
노트:

Logto Cloud에서 사용자 지정 도메인을 사용하는 경우, host_permissions를 도메인에 맞게 업데이트해야 합니다.

백그라운드 스크립트 (서비스 워커) 설정​

Chrome 확장의 백그라운드 스크립트에서 Logto SDK를 초기화하세요:

service-worker.js
import LogtoClient from '@logto/chrome-extension';

export const logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>'
appId: '<your-logto-app-id>',
});

<your-logto-endpoint> 및 <your-logto-app-id>를 실제 값으로 교체하세요. 이 값들은 Logto Console에서 방금 생성한 애플리케이션 페이지에서 찾을 수 있습니다.

백그라운드 스크립트가 없는 경우, 공식 가이드를 따라 하나를 생성할 수 있습니다.

정보:

왜 백그라운드 스크립트가 필요한가요?

팝업이나 옵션 페이지와 같은 일반 확장 페이지는 백그라운드에서 실행될 수 없으며, 인증 과정 중에 닫힐 가능성이 있습니다. 백그라운드 스크립트는 인증 과정을 적절히 처리할 수 있도록 보장합니다.

그런 다음, 다른 확장 페이지로부터 메시지를 듣고 인증 과정을 처리해야 합니다:

service-worker.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// 아래 코드에서는 각 작업에 대해 `true`를 반환하므로, `sendResponse`를 호출하여
// 발신자에게 알림을 보내야 합니다. 여기서 오류를 처리하거나 다른 방법으로 발신자에게 알릴 수도 있습니다.

if (message.action === 'signIn') {
const redirectUri = chrome.identity.getRedirectURL('/callback');
logtoClient.signIn(redirectUri).finally(sendResponse);
return true;
}

if (message.action === 'signOut') {
const redirectUri = chrome.identity.getRedirectURL();
logtoClient.signOut(redirectUri).finally(sendResponse);
return true;
}

return false;
});

위 코드에서 두 개의 리디렉션 URI가 사용된 것을 알 수 있습니다. 이들은 모두 chrome.identity.getRedirectURL에 의해 생성되며, 이는 인증 흐름을 위한 리디렉션 URL을 생성하는 Chrome 내장 API입니다. 두 URI는 다음과 같습니다:

  • 로그인용 https://<extension-id>.chromiumapp.org/callback.
  • 로그아웃용 https://<extension-id>.chromiumapp.org/.

이 URI들은 접근할 수 없으며, Chrome이 인증 과정의 특정 작업을 트리거하기 위해 사용됩니다.

Logto 애플리케이션 설정 업데이트​

이제 방금 생성한 리디렉션 URI를 허용하도록 Logto 애플리케이션 설정을 업데이트해야 합니다.

  1. Logto Console에서 애플리케이션 페이지로 이동합니다.
  2. "리디렉션 URI" 섹션에서 URI를 추가합니다: https://<extension-id>.chromiumapp.org/callback.
  3. "로그아웃 후 리디렉션 URI" 섹션에서 URI를 추가합니다: https://<extension-id>.chromiumapp.org/.
  4. "CORS 허용 원본" 섹션에서 URI를 추가합니다: chrome-extension://<extension-id>. Chrome 확장에서 SDK는 이 원본을 사용하여 Logto API와 통신합니다.
  5. 변경 사항 저장을 클릭합니다.

<extension-id>를 실제 확장 ID로 교체하는 것을 잊지 마세요. 확장 ID는 chrome://extensions 페이지에서 찾을 수 있습니다.

팝업에 로그인 및 로그아웃 버튼 추가​

거의 다 왔습니다! 팝업 페이지에 로그인 및 로그아웃 버튼과 기타 필요한 로직을 추가해 봅시다.

popup.html 파일에서:

popup.html
<button id="sign-in">로그인</button> <button id="sign-out">로그아웃</button>

popup.js 파일에서 (popup.js가 popup.html에 포함되어 있다고 가정):

popup.js
document.getElementById('sign-in').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signIn' });
// 로그인 완료 (또는 실패), 여기서 UI를 업데이트할 수 있습니다.
});

document.getElementById('sign-out').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signOut' });
// 로그아웃 완료 (또는 실패), 여기서 UI를 업데이트할 수 있습니다.
});

체크포인트: 인증 흐름 테스트​

이제 Chrome 확장에서 인증 흐름을 테스트할 수 있습니다:

  1. 확장 팝업을 엽니다.
  2. "로그인" 버튼을 클릭합니다.
  3. Logto 로그인 페이지로 리디렉션됩니다.
  4. Logto 계정으로 로그인합니다.
  5. Chrome으로 다시 리디렉션됩니다.

인증 상태 확인​

Chrome은 통합된 저장소 API를 제공하므로, 로그인 및 로그아웃 흐름 외에도 모든 Logto SDK 메서드를 팝업 페이지에서 직접 사용할 수 있습니다.

popup.js에서, 백그라운드 스크립트에서 생성한 LogtoClient 인스턴스를 재사용하거나 동일한 구성으로 새로 생성할 수 있습니다:

popup.js
import LogtoClient from '@logto/chrome-extension';

const logtoClient = new LogtoClient({
endpoint: '<your-logto-endpoint>'
appId: '<your-logto-app-id>',
});

// 또는 백그라운드 스크립트에서 생성한 logtoClient 인스턴스를 재사용
import { logtoClient } from './service-worker.js';

그런 다음 인증 상태와 사용자의 프로필을 로드하는 함수를 생성할 수 있습니다:

popup.js
const loadAuthenticationState = async () => {
const isAuthenticated = await logtoClient.isAuthenticated();
// 인증 상태에 따라 UI를 업데이트합니다.

if (isAuthenticated) {
const user = await logtoClient.getIdTokenClaims(); // { sub: '...', email: '...', ... }
// 사용자의 프로필로 UI를 업데이트합니다.
}
};

loadAuthenticationState 함수를 로그인 및 로그아웃 로직과 결합할 수도 있습니다:

popup.js
document.getElementById('sign-in').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signIn' });
await loadAuthenticationState();
});

document.getElementById('sign-out').addEventListener('click', async () => {
await chrome.runtime.sendMessage({ action: 'signOut' });
await loadAuthenticationState();
});

다음은 인증 상태가 포함된 팝업 페이지의 예입니다:

팝업 페이지

기타 고려 사항​

  • 서비스 워커 번들링: Webpack이나 Rollup과 같은 번들러를 사용하는 경우, Node.js 모듈의 불필요한 번들링을 피하기 위해 명시적으로 browser 또는 유사한 대상으로 설정해야 합니다.
  • 모듈 해상도: Logto Chrome 확장 SDK는 ESM 전용 모듈입니다.

TypeScript, Rollup 및 기타 구성과 함께 완전한 예제를 보려면 샘플 프로젝트를 참조하세요.

사용자 정보 가져오기​

사용자 정보 표시​

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

Home.js
const userInfo = await logtoClient.getIdTokenClaims();

// ID 토큰 클레임 (Claims)을 위한 표시 테이블 생성
const table = document.createElement('table');
const thead = document.createElement('thead');
const tr = document.createElement('tr');
const thName = document.createElement('th');
const thValue = document.createElement('th');
thName.innerHTML = 'Name';
thValue.innerHTML = 'Value';
tr.append(thName, thValue);
thead.append(tr);
table.append(thead);

const tbody = document.createElement('tbody');

for (const [key, value] of Object.entries(userInfo)) {
const tr = document.createElement('tr');
const tdName = document.createElement('td');
const tdValue = document.createElement('td');
tdName.innerHTML = key;
tdValue.innerHTML = typeof value === 'string' ? value : JSON.stringify(value);
tr.append(tdName, tdValue);
tbody.append(tr);
}

table.append(tbody);

추가 클레임 (Claims) 요청​

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

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

정보:

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

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

팁:

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

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

추가 스코프 (Scopes)를 요청하려면 Logto 설정을 구성할 수 있습니다:

index.js
import LogtoClient, { UserScope } from '@logto/browser';

const logtoClient = new LogtoClient({
appId: '<your-application-id>',
endpoint: '<your-logto-endpoint>',
scopes: [UserScope.Email, UserScope.Phone],
});

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

const claims = await getIdTokenClaims();
// 이제 추가 클레임 (Claims) `claims.email`, `claims.phone` 등에 접근할 수 있습니다.

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

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

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

스코프 (Scopes)와 클레임 (Claims)​

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 토큰에 추가로 포함되도록 구성할 수 있습니다.

지원되는 스코프와 해당 클레임(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를 구성할 때 이를 추가할 수 있습니다:

index.js
import LogtoClient from '@logto/browser';

const logtoClient = new LogtoClient({
// ...other configs
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를 구성할 때 추가할 수 있습니다:

index.js
import LogtoClient from '@logto/chrome-extension';

const logtoClient = new LogtoClient({
// ...other configs
scopes: ['shopping:read', 'shopping:write', 'store:read', 'store:write'],
resources: ['https://shopping.your-app.com/api', 'https://store.your-app.com/api'], // API 리소스를 추가하세요
});

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

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

index.js
import LogtoClient, { UserScope } from '@logto/chrome-extension';

const logtoClient = new LogtoClient({
// ...other configs
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 accessToken = await logtoClient.getAccessToken('https://store.your-app.com/api');
console.log('액세스 토큰', accessToken);

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

조직 토큰 가져오기​

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

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

index.js
import LogtoClient, { UserScope } from '@logto/chrome-extension';

const logtoClient = new LogtoClient({
// ...other configs
scopes: [UserScope.Organizations],
});

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

index.js
// 사용자 정보에서 organizationIds 가져오기

const claims = await logtoClient.getIdTokenClaims();
const organizationIds = claims.organizations;

/**
* 또는 ID 토큰 클레임에서 가져오기
*
* const claims = await logtoClient.getIdTokenClaims();
* const organizationIds = claims.organizations;
*/

// 조직 액세스 토큰 가져오기
if (organizationIds.length > 0) {
const organizationId = organizationIds[0];
const organizationAccessToken = await logtoClient.getOrganizationToken(organizationId);
console.log('조직 액세스 토큰', organizationAccessToken);
}

./code/_scopes-and-claims-code.mdx./code/_config-organization-code.mdx

요청 헤더에 액세스 토큰 첨부하기​

토큰을 HTTP 헤더의 Authorization 필드에 Bearer 형식 (Bearer YOUR_TOKEN)으로 넣으면 됩니다.

노트:

Bearer 토큰의 통합 흐름은 사용 중인 프레임워크나 요청자에 따라 다를 수 있습니다. 요청 Authorization 헤더를 적용하는 방법을 선택하세요.

추가 읽을거리​

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