Usage¶
The main entry point is the Lokksmith class, which manages and persists authentication clients and
their state across your application. To obtain an instance, use the platform-specific
createLokksmith() function. The function has an optional options argument for tweaking the
behaviour of Lokksmith. Please read the source code documentation for details.
Note
It is recommended to create a single shared Lokksmith instance, ideally provided via
dependency injection.
Info
The factory function differs slightly per platform:
- On Android it also requires the current
Context. - On Desktop it requires a
dataDirectoryspecifying where Lokksmith stores its data, for examplecreateLokksmith(dataDirectory = DataDirectory.Default("my-app")). - On Web it optionally accepts
handleRedirectOnStartup(defaulttrue) to automatically complete an auth flow after the redirect; see Web.
Use getOrCreate() to retrieve an existing client by its unique key, or create a new one if it does
not exist. This key is independent of the OAuth client ID, but you may use the same value if it
suits your use case. Defining distinct keys allows you to manage multiple authentication clients
within your application.
val client = lokksmith.getOrCreate("my-key") {
id = "my-client-id"
discoveryUrl = "https://example.com/.well-known/openid-configuration"
}
Note
Many functions of Lokksmith are suspending and must be run in a Coroutine,
like getOrCreate() in this case.
Once you have a client instance, it is time to call the Authorization Code Flow to obtain the tokens.
val authFlow = client.authorizationCodeFlow(
AuthorizationCodeFlow.Request( // (1)!
redirectUri = "my-app://openid-response"
)
)
val initiation = authFlow.prepare()
- See code documentation of
AuthorizationCodeFlow.Requestfor more details.
The next step is to call the request URL from the Initiation object and pass the returned response
to the auth flow.
// Open system browser with initiation.requestUrl, pass response to auth flow
authFlow.onResponse(response)
Note
See platform-specific implementation details below.
In a best case scenario the client is now authenticated and received the tokens.
You can now access the tokens through client.tokens, which is a Coroutine Flow,
or client.runWithTokens().
Note
The tokens Flow does not automatically refresh tokens when they expire.
Use runWithTokens() to ensure fresh tokens when required.
Singleton¶
A singleton Lokksmith instance must be provided during application startup as soon as possible via
SingletonLokksmithProvider. This provider ensures that platform-specific response handling, which
is decoupled from the initiation of an auth flow and is launched by the system implicitly, is able
to retrieve the Lokksmith instance. On Android this should be executed in the Application
class, for example.
Platform-specific configuration¶
Some request values legitimately differ per platform. The most common is the redirect URI: a
multiplatform app often registers a different URI per platform — for example a verified
App Link / Universal Link such as
https://app.example.com/redirect on Android and iOS, but a same-origin URL like
https://example.com/redirect on Web. On Desktop the redirect URI is ignored
and replaced by the loopback URL, so its value does not matter there.
Keep your shared flow code identical by delegating the platform-dependent value to an
expect/actual declaration:
actual val redirectUri = "http://localhost/callback" // ignored; replaced by the loopback URL
Then build the request from shared code as usual:
val authFlow = client.authorizationCodeFlow(
AuthorizationCodeFlow.Request(redirectUri = redirectUri)
)
Tip
If more than the redirect URI differs between platforms, delegate the construction of the whole
AuthorizationCodeFlow.Request (and EndSessionFlow.Request) to an expect/actual function
instead of just the URI.
Platform implementations¶
Calling the system browser and handling the authentication response on mobile platforms involves multiple steps, including managing process death and app recreation. To ensure a seamless user experience, it is essential to persist and restore authentication state as needed. Lokksmith provides platform-specific implementations that abstract these complexities, making it easier to integrate secure authentication flows in your application.
Compose Multiplatform¶
The AuthFlowLauncher shown here works on Android, iOS, Desktop and Web.
Once you receive the Initiation object, use AuthFlowLauncher to start the authentication flow
from your Composable. For example:
val uiState by viewModel.uiState.collectAsStateWithLifecycle() // (1)!
val authFlowLauncher = rememberAuthFlowLauncher()
LaunchedEffect(uiState.initiation) {
uiState.initiation?.let { initiation ->
authFlowLauncher.launch(initiation)
}
}
data class UiState(val initiation: Initiation? = null)
Tip
launch accepts an optional options argument that allows you to customize Lokksmith's
behavior. For example, on Android, you can choose between authentication using a Custom Tab or
an Auth Tab.
You can either use authFlowLauncher.result to observe the current state of the process and update
the user interface accordingly or use Client.authFlowResult(1) from your business logic
(e.g. ViewModel) to pass the same result state to your UI state.
- See
AuthFlowResultProvider
iOS¶
The iOS integration is currently usable from a Kotlin Multiplatform or Compose Multiplatform application. A dedicated Swift package for use in a native iOS app is still pending.
To launch an authentication flow from the iOS platform code of a Kotlin Multiplatform application,
use launchAuthFlow():
Desktop¶
On Desktop, Lokksmith implements the loopback redirect described in RFC 8252
("OAuth 2.0 for Native Apps"). When you prepare an auth flow, Lokksmith starts a temporary HTTP
server bound to 127.0.0.1 on an ephemeral port and uses http://127.0.0.1:<port>/callback as the
redirect URI. The redirectUri you pass to AuthorizationCodeFlow.Request (or
EndSessionFlow.Request) is therefore ignored and replaced by this loopback URL.
Warning
Because the port is chosen at runtime, your OpenID provider must allow loopback redirect URIs
(http://127.0.0.1 / http://localhost) with an arbitrary port, as recommended by RFC 8252. No
custom URI scheme or manifest configuration is required on Desktop.
Use the Compose rememberAuthFlowLauncher() exactly as described under
Compose Multiplatform. It opens the system browser, waits for the redirect
on the loopback server, and completes the flow.
The Desktop behaviour can be customized via DesktopOptions when creating the instance:
val lokksmith = createLokksmith(
dataDirectory = DataDirectory.Default("my-app"),
desktop = DesktopOptions(
redirectPath = "/callback", // (1)!
redirectTimeout = 5.minutes, // (2)!
// browserLauncher, authorizationResponseHtml, endSessionResponseHtml, ...
),
)
- Path of the loopback redirect URI.
- How long to wait for the redirect before the flow times out.
Tip
See the Demo for a complete, runnable Desktop example, including how to test the flow against a local OpenID provider.
Web¶
On the Web (Kotlin/Wasm in the browser) an auth flow is completed via a full-page redirect: the
browser navigates to the OpenID provider and is redirected back to a same-origin URL, which
reloads and restarts the whole application. The redirectUri you pass to
AuthorizationCodeFlow.Request (or EndSessionFlow.Request) must therefore be a URL on your app's
own origin, for example https://myapp.example/. Custom URI schemes do not work in the browser.
Because the redirect restarts the app, there is no in-memory state to resume from. Lokksmith reads
the response from the current URL on startup for you: by default createLokksmith() completes a
pending flow automatically (controlled by the handleRedirectOnStartup argument). The result is then
observed through common code via Client.authFlowResult or client.tokens, exactly as on the other
platforms.
Use the Compose rememberAuthFlowLauncher() exactly as described under
Compose Multiplatform to start the flow. From non-Compose code you can
launch it directly, which navigates the current document to the request URL:
Manual redirect handling
To control when the redirect is processed, create the instance with
createLokksmith(handleRedirectOnStartup = false) and call
lokksmith.completeAuthFlowFromRedirect() yourself, for example during application startup.
Security
On the Web, Lokksmith persists its state — including tokens — in the browser's localStorage,
which is not encrypted and is readable by any script on the same origin. A cross-site
scripting (XSS) vulnerability can therefore expose tokens. Apply a strong Content Security Policy
and the usual XSS defenses.
Note
rememberAuthFlowLauncher().result is not restored after the full-page reload. Observe
Client.authFlowResult (for example from your ViewModel) to react to the completed result.