Capacitor 앱에서 Auth0로 로그인하려면 네이티브 Auth0에 애플리케이션을 생성하고, 다음과 같은 커스텀 스킴 콜백 URL을 등록합니다 com.example.app://oauth/auth0, 그리고 시스템 브라우저를 통해 PKCE를 사용하는 OpenID Connect 인가 code 플로우를 실행합니다. 이를 사용하면 @capgo/capacitor-social-login 즉, 하나의 initialize Auth0 엔드포인트를 사용하는 호출과 하나의 SocialLogin.login({ provider: 'oauth2', options: { providerId: 'auth0' } }).
이 가이드는 Auth0 대시보드 설정, iOS/Android/웹용 클라이언트 code, API 액세스 토큰, 리프레시 토큰, 로그아웃 및 마주칠 수 있는 오류를 다룹니다. 2026년 10월 기준 Capacitor 8을 대상으로 작성되었습니다.
Capacitor에서 Auth0 플로우가 작동하는 방식
Auth0가 로그인 페이지(Universal Login)를 호스팅합니다. 앱은 비밀번호를 절대 보지 않습니다. 플로우는 다음과 같습니다:
- 앱은 PKCE verifier와 challenge를 생성한 후
https://YOUR_TENANT/authorize보안 브라우저에서 열립니다. - 사용자가 로그인합니다(비밀번호, 소셜, 패스키, MFA 등 Auth0에서 설정한 방식).
- Auth0는 콜백 URL로 리다이렉트되며, 일회용
code. - 앱은 code와 PKCE verifier를
/oauth/tokenID 토큰, 액세스 토큰, 그리고 선택적으로 리프레시 토큰으로 교환합니다.
사용되는 브라우저는 플랫폼에 따라 다릅니다:
| 플랫폼 | 브라우저 | 콜백 처리 |
|---|---|---|
| iOS | ASWebAuthenticationSession |
시스템이 처리하며, Info.plist 변경 |
| Android | 기본적으로 내장 WebView 또는 Chrome Custom Tabs와 함께 androidUseCustomTabs: true |
Custom Tabs에는 intent filter가 필요합니다 |
| 웹 | 팝업(기본) 또는 전체 페이지 리다이렉트 | handleRedirectCallback() 리턴 페이지에서 |
Android에서는 Custom Tabs를 사용하세요. Chrome과 쿠키를 공유하고, 패스키 및 비밀번호 관리자를 지원하며, Auth0가 기대하는 RFC 8252(네이티브 앱용 OAuth)를 따릅니다.
1단계: Auth0 애플리케이션 설정
- 다음 Auth0 대시보드, 로 이동 Applications > Applications > Create Application.
- 다음을 선택하세요: 네이티브 을(를) 생성하세요.
- 한편, 설정 탭에서 Domain (예를 들어
example.eu.auth0.com) 및 클라이언트 ID. - 아래 인증 정보, 인증 방법이 없음인지 확인하세요. PKCE와 함께 사용되는 public-client 설정입니다.
Callback 및 logout URL
다음 필드를 입력하세요 Settings > Application URIs:
| 필드 | 모바일 값 | 웹 값 |
|---|---|---|
| 허용된 Callback URL | com.example.app://oauth/auth0 |
https://app.example.com/auth/callback |
| 허용된 Logout URL | com.example.app://oauth/logout |
https://app.example.com/ |
| 허용된 웹 오리진 | 필요 없음 | https://app.example.com |
여러 값은 쉼표로 구분하세요. Capacitor를 appId 스키마로 사용하세요. 역도메인 스키마는 다른 앱과의 충돌을 방지합니다. 웹 빌드도 함께 배포한다면 많은 팀이 별도의 단일 페이지 애플리케이션 을 Auth0에 만들어 웹과 모바일 설정이 섞이지 않도록 합니다.
Refresh token
사용자가 로그인 상태를 유지해야 한다면:
- 다음에서 설정 > Refresh Token Rotation에서 로테이션을 켜세요. 리프레시할 때마다 새 리프레시 토큰이 발급되고 이전 토큰은 무효화됩니다.
- 제품에 맞는 절대 수명과 비활성 수명을 설정하세요.
- 자체 API를 호출하는 경우, 열기 Applications > APIs > 해당 API > Settings 을(를) 활성화하고 오프라인 액세스 허용.
- 앱에서
offline_accessscope를 요청하세요.
API 대상
백엔드에서 Auth0 액세스 토큰을 검증하는 경우, 아래에서 API를 생성하세요 Applications > APIs 식별자는 다음과 같은 값으로 설정하세요 https://api.example.com. 해당 식별자를 audience으로 전달하세요. 없으면 액세스 토큰은 Auth0에만 유효합니다. /userinfo 엔드포인트입니다.
2단계: 플러그인 설치 및 설정
bun add @capgo/capacitor-social-login
bunx cap sync
Auth0가 유일한 제공자라면 바이너리 크기를 줄이기 위해 번들된 소셜 SDK를 비활성화하세요:
// capacitor.config.ts
plugins: {
SocialLogin: {
providers: { google: false, facebook: false, apple: false, twitter: false },
},
},
일반적인 OAuth2 엔진은 해당 SDK에 의존하지 않습니다.
Custom Tabs용 Android intent 필터
콜백 스킴을 다음에 추가하세요 MainActivity 에서 android/app/src/main/AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.example.app" android:host="oauth" />
</intent-filter>
Capacitor 템플릿은 이미 다음을 설정합니다 android:launchMode="singleTask" 켜짐 MainActivity, 리다이렉트에 필요합니다. 그대로 유지하세요.
3단계: Auth0 제공자 초기화
import { Capacitor } from '@capacitor/core';
import { SocialLogin } from '@capgo/capacitor-social-login';
const AUTH0_DOMAIN = 'https://example.eu.auth0.com';
const isWeb = Capacitor.getPlatform() === 'web';
export async function initAuth() {
await SocialLogin.initialize({
oauth2: {
auth0: {
appId: 'YOUR_AUTH0_CLIENT_ID',
authorizationBaseUrl: `${AUTH0_DOMAIN}/authorize`,
accessTokenEndpoint: `${AUTH0_DOMAIN}/oauth/token`,
resourceUrl: `${AUTH0_DOMAIN}/userinfo`,
redirectUrl: isWeb
? 'https://app.example.com/auth/callback'
: 'com.example.app://oauth/auth0',
scope: 'openid profile email offline_access',
pkceEnabled: true,
additionalParameters: {
audience: 'https://api.example.com',
},
logoutUrl: `${AUTH0_DOMAIN}/oidc/logout`,
postLogoutRedirectUrl: isWeb
? 'https://app.example.com/'
: 'com.example.app://oauth/logout',
androidUseCustomTabs: true,
},
},
});
}
명시적인 엔드포인트를 다음으로 교체할 수 있습니다 issuerUrl: AUTH0_DOMAIN. 플러그인이 다음을 읽습니다 /.well-known/openid-configuration. 명시적으로 설정한 값이 자동 감지된 값보다 우선하며, Auth0 커스텀 도메인을 사용할 때 유용합니다.
Ionic Auth Connect에서 이전하는 경우, 플러그인은 또한 다음을 내보냅니다 SocialLoginAuthConnect 다음과 함께 auth0 다음 인수를 받는 프리셋 domain, clientId, redirectUrl 및 audience. 구현 세부 사항은 Auth Connect 마이그레이션 가이드.
4단계: 로그인
import { SocialLogin } from '@capgo/capacitor-social-login';
export async function signInWithAuth0() {
try {
const { result } = await SocialLogin.login({
provider: 'oauth2',
options: { providerId: 'auth0' },
});
return {
idToken: result.idToken,
accessToken: result.accessToken?.token,
expiresAt: result.accessToken?.expires,
refreshToken: result.refreshToken,
user: result.resourceData, // JSON from /userinfo
};
} catch (error: any) {
if (error?.code === 'USER_CANCELLED') return null;
throw error;
}
}
유용한 로그인 옵션:
prompt: 'login'Auth0에 세션 쿠키가 남아 있어도 로그인 페이지를 강제 표시합니다.loginHint: 'user@example.com'이메일을 미리 입력합니다.additionalParameters: { connection: 'google-oauth2' }Universal Login을 건너뛰고 바로 하나의 연결로 이동합니다.additionalParameters: { screen_hint: 'signup' }회원가입 화면을 엽니다.
웹 리다이렉트 콜백
웹에서는 기본 팝업 플로우에 추가 설정이 필요 없습니다. 만약 flow: 'redirect'를 사용한다면, 콜백 페이지에서 결과를 읽어보세요:
const result = await SocialLogin.handleRedirectCallback();
if (result?.provider === 'oauth2') {
console.log(result.result.idToken);
}
handleRedirectCallback 는 웹 전용이며 iOS와 Android에서는 거부됩니다.
5단계: ID 토큰을 읽고 API 호출
디스플레이 목적으로만 클라이언트에서 ID 토큰을 디코딩하세요:
const { claims } = await SocialLogin.decodeIdToken({ idToken: result.idToken! });
console.log(claims.email, claims.name, claims.sub);
decodeIdToken 서명을 검증하지 않습니다. 보안 관련 작업에는 액세스 토큰을 API로 보내서 거기서 검증하세요:
import { createRemoteJWKSet, jwtVerify } from 'jose';
const jwks = createRemoteJWKSet(new URL('https://example.eu.auth0.com/.well-known/jwks.json'));
export async function verifyAuth0AccessToken(token: string) {
const { payload } = await jwtVerify(token, jwks, {
issuer: 'https://example.eu.auth0.com/', // Auth0 issuers end with a slash
audience: 'https://api.example.com',
});
return payload; // payload.sub is the Auth0 user ID
}
6단계: 액세스 토큰 갱신
import { SocialLogin } from '@capgo/capacitor-social-login';
export async function getValidAccessToken() {
const { isLoggedIn } = await SocialLogin.isLoggedIn({ provider: 'oauth2', providerId: 'auth0' });
if (!isLoggedIn) {
// access token expired: try the stored refresh token
try {
await SocialLogin.refresh({ provider: 'oauth2', options: { providerId: 'auth0' } });
} catch {
// refresh token expired, revoked or already rotated: sign in again
const loginResult = await signInWithAuth0();
if (!loginResult) throw new Error('Sign-in cancelled');
}
}
const { accessToken } = await SocialLogin.getAuthorizationCode({
provider: 'oauth2',
providerId: 'auth0',
});
return accessToken;
}
refresh 리프레시 토큰이 만료되거나 폐기된 경우 거부됩니다. 로테이션이 켜져 있으면 이미 사용된 리프레시 토큰도 실패하므로, 항상 거부 시 사용자를 다시 로그인 화면으로 보내는 처리를 해야 합니다.
refresh 로그인 시 플러그인이 저장한 리프레시 토큰을 사용합니다. 리프레시 토큰을 직접 관리하는 경우, 다음을 호출하세요: SocialLogin.refreshToken({ provider: 'oauth2', providerId: 'auth0', refreshToken }), 전체 새 토큰 응답을 반환합니다. 로테이션이 켜져 있으면 매번 새 리프레시 토큰을 저장하세요.
플러그인은 앱 전용 저장소에 토큰 사본을 유지합니다. 보안 검토에서 Keychain 또는 Keystore를 요구하는 경우, 리프레시 토큰을 NativeBiometric.setData({ key, value }) 과 @capgo/capacitor-native-biometric 에 저장하고 refreshToken 에 전달하세요. 저희 안전한 토큰 저장 가이드에서는 에서 옵션을 비교합니다.
7단계: 로그아웃
await SocialLogin.logout({ provider: 'oauth2', providerId: 'auth0' });
플러그인은 저장된 토큰을 삭제한 후 logoutUrl 포함 id_token_hint 및 post_logout_redirect_uri 그래서 Auth0가 세션 쿠키를 삭제합니다. Auth0의 /oidc/logout 엔드포인트는 해당 파라미터를 이해하며, 리다이렉트 대상은 다음에 나열되어야 합니다: 허용된 로그아웃 URL.
iOS와 Android에서는 종료 세션 페이지가 시스템 브라우저에서 열리므로 사용자가 잠시 앱을 벗어납니다. 로컬 로그아웃만 원한다면 logoutUrl 을(를) 생략하고 prompt: 'login' 다음 로그인 시 전달하여 기억된 Auth0 세션이 사용자를 즉시 다시 로그인하지 않도록 하세요. iosPrefersEphemeralSession: true 을(를) 설정하면 iOS에서 공유 쿠키 문제를 피할 수 있지만, 앱 간 SSO가 불가능해집니다.
문제 해결
| 오류 | 원인 | 해결 방법 |
|---|---|---|
Callback URL mismatch |
리다이렉트 URL이 허용된 콜백 URL에 없습니다 | code의 정확한 값을 대시보드에 복사하고, 스킴과 호스트를 확인하세요 |
Unauthorized 또는 invalid_client 로 /oauth/token |
앱 유형이 Native이 아니거나, 인증 방식이 None이 아닙니다 | Native으로 다시 생성하고, 토큰 엔드포인트 인증을 None으로 설정하세요 |
아니요 refreshToken 결과에 |
offline_access 누락되었거나 API에서 오프라인 액세스가 허용되지 않습니다 |
스코프를 추가하고, 오프라인 액세스 허용을 활성화하며, 로테이션을 활성화하세요 |
| API에서 액세스 토큰을 검증할 수 없습니다 | 아니요 audience 전송됨 |
추가 audience 에서 additionalParameters |
| Android에서 로그인 후 앱이 다시 열리지 않음 | Intent 필터의 scheme 또는 host가 일치하지 않음 redirectUrl |
일치 com.example.app 및 oauth 완전히 |
OAuth2 provider "auth0" not configured |
login 이전에 호출됨 initialize 또는 키 불일치 |
Await initialize 시작 시, 동일한 providerId |
| 로그아웃 페이지가 계속 열려 있음 | post_logout_redirect_uri Allowed Logout URLs에 없음 |
추가하거나, 로컬 전용 로그아웃을 사용하세요 |
| 토큰 교환 시 Web CORS 오류 | Origin이 Allowed Web Origins에 없음 | Web origin을 추가하거나, Web용 SPA 애플리케이션을 사용하세요 |
설정 logsEnabled: true provider 설정에 넣으면 디버깅하는 동안 생성된 URL과 토큰 교환 세부 정보가 출력됩니다.
스토어 릴리스 없이 인증 수정 사항을 배포하세요
스코프, audience, 오류 처리 변경은 TypeScript만 고치면 됩니다. Capgo Live Update 사용하기 을(를) 사용하면 해당 수정 사항을 사용자에게 당일 배포할 수 있습니다. 새 intent filter 같은 네이티브 변경 사항은 여전히 새 바이너리가 필요하며, 이는 Capgo 빌드 클라우드에서 생성할 수 있습니다.