Capacitor 앱에서 Okta로 로그인하려면 OIDC 네이티브 애플리케이션 을 Okta 관리 콘솔에서 생성하고, 로그인 리다이렉트 URI(예: )를 등록한 후 com.example.app://oauth/okta시스템 브라우저에서 PKCE를 사용하여 인가 code 플로우를 실행합니다. @capgo/capacitor-social-login 을 사용하면 이서와 클라이언트 ID로 Okta를 한 번만 설정한 후 SocialLogin.login({ provider: 'oauth2', options: { providerId: 'okta' } }).
이 가이드는 Okta 앱 통합, 인가 서버 정책, iOS/Android/웹용 플러그인 code, 토큰 갱신, 사용자 프로필, 로그아웃 및 가장 자주 발생하는 Okta 오류를 다룹니다. 2026년 10월 기준 Capacitor 8을 대상으로 합니다.
준비 사항
- 앱 통합을 생성할 수 있는 관리자 권한이 있는 Okta 조직(Workforce Identity 또는 Customer Identity).
- Capacitor 8 앱.
- 내 Capacitor
appId, 이를 리다이렉트 스킴으로 재사용합니다. - 선택적으로 Okta 액세스 토큰을 검증하는 백엔드 API.
Capacitor 앱에서 Okta 로그인이 작동하는 방식
앱은 비밀번호 입력 폼을 표시하지 않습니다. 대신 보안 브라우저에서 Okta 로그인 페이지를 열고, Okta가 MFA와 정책을 처리한 뒤 code와 함께 앱으로 다시 리다이렉트합니다. 앱은 code와 PKCE 검증자를 토큰으로 교환합니다.
| 플랫폼 | 브라우저 | 설정할 항목 |
|---|---|---|
| iOS | ASWebAuthenticationSession |
없음 Info.plist |
| Android | Chrome Custom Tabs 사용 androidUseCustomTabs: true (기본값: WebView) |
리다이렉트 스킴용 Intent 필터 |
| 웹 | 팝업 또는 리다이렉트 | CORS용 신뢰할 수 있는 Origin |
Android에서는 Custom Tabs 사용을 권장합니다. Okta Verify, FastPass, passkey는 임베디드 WebView보다 시스템 브라우저에서 더 잘 동작하고, SSO 쿠키도 Chrome과 공유됩니다.
1단계: Okta 앱 통합 생성
- Okta Admin Console에서 다음으로 이동하세요: Applications > Applications > Create App Integration.
- 선택 OIDC - OpenID Connect 및 네이티브 애플리케이션.
- 이름을 지정한 후, 아래에서 권한 부여 유형 유지 Code 인증 을(를) 선택하고 Refresh Token 사용자가 로그인 상태를 유지해야 하는 경우
- 로그인 리다이렉트 URI:
com.example.app://oauth/okta. 추가하세요https://app.example.com/auth/callback웹 빌드를 배포하는 경우 - Sign-out 리다이렉트 URI:
com.example.app://oauth/logout(필요한 경우 웹 URL 포함). - 할당: 앱 사용자를 지정합니다. 할당되지 않은 사용자는 로그인 시 오류가 발생합니다.
- 저장한 후 클라이언트 ID 아래 클라이언트 자격 증명에서 클라이언트 인증은 없음 및 PKCE 필수 이 켜져 있어야 합니다.
Okta는 역도메인 형식의 private-use scheme을 사용하는 네이티브 리다이렉트 URI를 요구합니다. 번들 ID 또는 애플리케이션 ID가 해당됩니다.
인증 서버 확인
다음으로 이동: Security > API > Authorization Servers. 대부분의 설정은 default 커스텀 인증 서버, issuer https://your-org.okta.com/oauth2/default.
| Issuer | 사용 시점 |
|---|---|
https://your-org.okta.com/oauth2/default (커스텀) |
자체 API가 액세스 토큰을 검증하므로, 커스텀 scope 또는 claim이 필요합니다 |
https://your-org.okta.com (조직) |
ID 토큰만 사용하거나 Okta API를 호출하면 됩니다 |
서버의 액세스 정책 탭을 엽니다. 앱(또는 All clients)에 해당 Code 인증 권한 부여, 그리고 Refresh Token 사용하는 경우입니다. 규칙이 누락되면 “Policy evaluation failed for this request” 오류가 발생합니다.
웹용 Trusted Origins
웹에서 동일한 code를 실행하는 경우, 아래 항목에 웹 오리진을 추가하세요. 보안 > API > 신뢰할 수 있는 출처 포함 CORS 확인했습니다. 없으면 브라우저가 토큰 요청을 차단합니다.
2단계: 플러그인 설치
bun add @capgo/capacitor-social-login
bunx cap sync
Okta가 유일한 제공자라면 번들된 소셜 SDK를 capacitor.config.ts:
plugins: {
SocialLogin: {
providers: { google: false, facebook: false, apple: false, twitter: false },
},
},
3단계: Android 리다이렉트 인텐트 필터 추가
다음에서 android/app/src/main/AndroidManifest.xml, 안에 MainActivity activity:
<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>
MainActivity 은(는) 유지해야 합니다 android:launchMode="singleTask". 이는 Capacitor 기본값으로, 리다이렉트가 실행 중인 activity에 도달하도록 합니다.
4단계: Okta 초기화
import { Capacitor } from '@capacitor/core';
import { SocialLogin } from '@capgo/capacitor-social-login';
const OKTA_ISSUER = 'https://your-org.okta.com/oauth2/default';
const isWeb = Capacitor.getPlatform() === 'web';
export async function initAuth() {
await SocialLogin.initialize({
oauth2: {
okta: {
appId: 'YOUR_OKTA_CLIENT_ID',
authorizationBaseUrl: `${OKTA_ISSUER}/v1/authorize`,
accessTokenEndpoint: `${OKTA_ISSUER}/v1/token`,
resourceUrl: `${OKTA_ISSUER}/v1/userinfo`,
redirectUrl: isWeb
? 'https://app.example.com/auth/callback'
: 'com.example.app://oauth/okta',
scope: 'openid profile email offline_access',
pkceEnabled: true,
logoutUrl: `${OKTA_ISSUER}/v1/logout`,
postLogoutRedirectUrl: isWeb
? 'https://app.example.com/'
: 'com.example.app://oauth/logout',
androidUseCustomTabs: true,
},
},
});
}
더 짧은 대안: 설정 issuerUrl: OKTA_ISSUER 하고 엔드포인트 URL을 제거하세요. 플러그인이 로드합니다 /.well-known/openid-configuration 그리고 값을 채웁니다. Ionic Auth Connect에서 마이그레이션하는 팀은 SocialLoginAuthConnect 를 사용하여 okta 프리셋 (issuer, clientId, redirectUrl), 이에 대한 내용은 마이그레이션 가이드.
5단계: 로그인하고 프로필 읽기
import { SocialLogin } from '@capgo/capacitor-social-login';
export async function signInWithOkta() {
try {
const { result } = await SocialLogin.login({
provider: 'oauth2',
options: { providerId: 'okta' },
});
const profile = result.resourceData as {
sub: string;
email?: string;
name?: string;
preferred_username?: string;
} | null;
return {
accessToken: result.accessToken?.token,
idToken: result.idToken,
refreshToken: result.refreshToken,
scopes: result.scope,
profile,
};
} catch (error: any) {
if (error?.code === 'USER_CANCELLED') return null;
throw error;
}
}
resourceData 엔드포인트에서 반환된 JSON을 보관하므로 /userinfo 추가 요청 없이 프로필을 가져옵니다. 표시용 ID token claims를 읽으려면 SocialLogin.decodeIdToken({ idToken })를 사용하세요. 이 호출은 서명을 검증하지 않습니다.
알아두면 좋은 로그인 옵션:
loginHint사용자 이름을 미리 채웁니다.prompt: 'login'Okta 세션이 활성 상태여도 재인증을 강제합니다.additionalParameters: { idp: '0oa...' }Okta에 설정된 특정 identity provider로 바로 라우팅합니다.
웹에서 flow: 'redirect', 호출 SocialLogin.handleRedirectCallback() 콜백 페이지에서 교환을 완료합니다.
6단계: API에서 Okta 토큰 검증하기
import { createRemoteJWKSet, jwtVerify } from 'jose';
const ISSUER = 'https://your-org.okta.com/oauth2/default';
const jwks = createRemoteJWKSet(new URL(`${ISSUER}/v1/keys`));
export async function verifyOktaAccessToken(token: string) {
const { payload } = await jwtVerify(token, jwks, {
issuer: ISSUER,
audience: 'api://default', // the Audience value of your authorization server
});
return payload; // payload.uid is the Okta user ID, payload.sub the login
}
인증 서버의 탭에서 audience를 찾으세요. 설정 API에서 조직 인증 서버의 access token을 검증하지 마세요. Okta는 이를 지원하지 않습니다.
7단계: Refresh token
import { SocialLogin } from '@capgo/capacitor-social-login';
export async function ensureFreshOktaToken() {
const { isLoggedIn } = await SocialLogin.isLoggedIn({ provider: 'oauth2', providerId: 'okta' });
if (!isLoggedIn) {
await SocialLogin.refresh({ provider: 'oauth2', options: { providerId: 'okta' } });
}
const { accessToken } = await SocialLogin.getAuthorizationCode({ provider: 'oauth2', providerId: 'okta' });
return accessToken;
}
refresh token이 만료되었거나 폐기되었다면, refresh 거부됩니다. 이를 캐치해 사용자를 다시 signInWithOkta().
앱 통합의 리프레시 토큰 섹션을 확인하세요. 사용할 때마다 토큰 교체를 사용하면, refresh할 때마다 새로운 refresh token이 반환되고, 기존 토큰은 짧은 유예 기간이 지나면 더 이상 작동하지 않습니다. 플러그인은 저장된 복사본을 자동으로 교체합니다. refresh token을 직접 보관한다면 (예: Keychain을 통해 @capgo/capacitor-native-biometric setData), 사용하고 SocialLogin.refreshToken({ provider: 'oauth2', providerId: 'okta', refreshToken }) 반환된 값을 저장하세요.
8단계: 로그아웃
await SocialLogin.logout({ provider: 'oauth2', providerId: 'okta' });
플러그인이 저장된 토큰을 지운 뒤, /v1/logout 포함 id_token_hint 및 post_logout_redirect_uri를 엽니다. Okta는 세션을 종료하고 앱 통합에 등록되어 있어야 하는 sign-out URI로 리디렉션합니다. 모바일에서는 로그아웃 페이지가 시스템 브라우저에서 열립니다.
서버 측에서도 refresh token을 무효화하려면 Okta의 /v1/revoke endpoint를 로그아웃 전에 백엔드나 앱에서 호출하세요. 패턴은 토큰 무효화 가이드 를 참고하세요.
문제 해결
| 오류 | 원인 | 해결 방법 |
|---|---|---|
invalid_request: The 'redirect_uri' parameter must be a Login redirect URI in the client app settings |
리다이렉트가 등록되지 않았습니다 | Sign-in redirect URIs에 정확한 URI를 추가하세요 |
User is not assigned to the client application |
할당이 누락되었습니다 | Assignments에서 사용자 또는 그룹을 할당하세요 |
Policy evaluation failed for this request |
이 클라이언트 또는 grant에 대한 액세스 정책 규칙이 없습니다 | 인가 서버에 정책과 규칙을 추가하세요 |
invalid_client 토큰 엔드포인트에서 |
앱이 Web 또는 SPA 유형이거나, 인증 방법이 None이 아닙니다 | client authentication이 None인 Native 앱을 사용하세요 |
| refresh token이 없습니다 | Refresh Token grant가 꺼져 있거나, offline_access 누락되었거나, 규칙에 의해 차단됩니다 |
grant를 활성화하고, scope를 추가하고, 규칙에서 허용하세요 |
| API가 잘못된 issuer의 토큰을 거부합니다 | 토큰이 조직 서버에서 발급되었습니다 | 사용자 지정 인가 서버 issuer를 사용하세요 |
Web: CORS 오류 발생 /v1/token |
신뢰할 수 없는 Origin | CORS 설정에 신뢰할 수 있는 Origin 추가 |
| 로그인 후 Android에서 브라우저에 머무름 | Intent 필터 불일치 | 스키마와 호스트를 일치시킴 redirectUrl |
켜기 logsEnabled: true 디버깅 중 프로바이더 설정에서 authorize URL과 토큰 응답을 네이티브 로그에서 확인하려면
Okta 변경 사항 배포
엔터프라이즈 고객은 출시 후 정책이나 클레임을 변경하는 경우가 많습니다. 스코프, 클레임 처리, 에러 화면과 같은 클라이언트 측 변경 사항은 Capgo Live Update 사용하기 을 통해 전체 스토어 리뷰 없이 배포할 수 있습니다. 네이티브 부분(intent 필터, 새 스키마)은 여전히 새로운 빌드가 필요합니다.