Skip to main content

Native Apps - iOS, Android, React Native and Flutter

Native applications are installed on a device, such as a phone, a tablet or a desktop computer. Like any other application, a native app gets a client in Keycloak and signs users in with OpenID Connect. The app is a public client: it can't keep a secret, so it proves each login with PKCE instead. There are two good ways to build the login itself:

  • Native login with Phase Two's native authentication for Keycloak. The app signs users in with its own screens, and Keycloak still runs the authentication flow and decides what the user has to do. SDKs are available for iOS, Android, React Native and Flutter.
  • Browser-based login with a library such as AppAuth. The app opens Keycloak's login page in the system browser, and the browser hands the result back to the app. This works with any Keycloak and needs no extensions.

How native login works​

Phase Two's Keycloak extensions add a challenge endpoint to each realm, modeled on the IETF draft OAuth 2.0 for First-Party Applications. When the app starts a login, Keycloak runs an authentication flow you configure, and instead of rendering a login page it returns each step as a JSON challenge: a username, a password, a passkey assertion, a one-time code, a push approval. The SDK shows a native screen for each challenge and sends the answer back until Keycloak returns an authorization code, which the SDK exchanges for tokens at Keycloak's standard token endpoint with PKCE.

Because Keycloak is still running the flow, the settings you already rely on keep applying: conditional second factors, brute-force protection, required actions, and levels of authentication for step-up. You change the login in Keycloak, not in the app. Steps the native flow can't serve, such as a required action without a native version, hand the login to the system browser for that one sign-in.

The launch post explains why we built it.

Comparison​

Native login (Phase Two SDKs)Browser-based login (AppAuth)
Login screensThe app's own screens: default SwiftUI, Jetpack Compose, React Native and Flutter screens you can replace one at a timeKeycloak's login pages in a browser sheet, themed to match the app
First thing the user seesYour login screenOn iOS, a system prompt that the app "wants to use" the Keycloak domain to sign in, then the web page
Server requirementsPhase Two's native authentication extensions on Keycloak 26.4 to 26.8Any Keycloak
PlatformsiOS 17+, Android 9+ (API 28), React Native 0.84+, Flutter 3.35+iOS, macOS, Android and more
Sign-in methodsNative username, password, passkey, TOTP, email and SMS codes, recovery codes and push approval with number matchingEvery Keycloak authenticator
Required actionsUpdate password, verify email (by code), accept terms, register a passkey and enroll for push run in the app; others open the browserAll, in the browser
Registration, password reset, social and enterprise IdP loginIn the browserIn the browser
Browser single sign-onNo: a native login doesn't create a browser sessionYes: the browser can reuse an existing Keycloak session
Token storageKeychain or Android Keystore, handled by the SDKUp to the app
DPoP token bindingBuilt in, with a hardware-backed key where the device has oneNot built into AppAuth
MaturityBelow 1.0; APIs may change between releasesMature and widely used
LicenseSDKs Apache 2.0; Keycloak extensions Elastic License 2.0Apache 2.0

Which should I choose?​

  • Choose native login when the login is part of your product's experience: you want your own screens, passkeys and push approvals that feel like part of the app, no browser sheet or iOS consent prompt, and step-up for sensitive actions. It's for first-party apps, the apps you publish yourself, because the app sees what the user types.
  • Choose browser-based login when you can't add extensions to your Keycloak, when you rely on authenticators or identity providers that only run in the browser for most of your users, when several apps (or the app and your website) should share one browser session, or when the app belongs to a third party.
  • Mixing is fine. A native-login app still uses the browser for registration, password reset and social login, and the client settings for both approaches can live on the same Keycloak client.

Why not the password grant or an embedded webview?​

The password grant (Resource Owner Password Credentials) has the app post the username and password straight to the token endpoint. It only handles passwords, it bypasses the login flow you configure, so passkeys, push approvals, required actions and most second factors can't work, and it trains users to type their password wherever they're asked. The OAuth 2.0 Security Best Current Practice (RFC 9700) says it must not be used, and OAuth 2.1 removes it. Native login gives you the same "our own screens" experience while Keycloak still runs the full flow.

An embedded webview (WKWebView, Android WebView) that loads Keycloak's login page looks like browser-based login but isn't. The app controls the webview, so it can read everything the user types and every cookie; the user has no address bar to check; the session isn't shared with the system browser; and passkey support is limited there, while some identity providers, Google among them, block sign-in from webviews altogether. OAuth 2.0 for Native Apps (RFC 8252) requires the system browser instead. If you want the login inside the app, use native login; if you want the browser, use the system browser through AppAuth.

Guides​

Native login:

  • Server setup: install the extensions, create the native flow and the client, and configure DPoP, passkeys and push.
  • iOS: the Swift package with SwiftUI screens.
  • Android: the Kotlin libraries with Jetpack Compose screens.
  • React Native: the TurboModule with React screens and an Expo config plugin.
  • Flutter: the plugin with Flutter widgets.

Browser-based login:

On Phase Two hosted Keycloak, contact us to have the native authentication extensions enabled on your cluster.