Integrations
Accept Danish MitID, Swedish BankID, Norwegian BankID and more eID logins in your Expo (React Native) app with @criipto/verify-expo.
Idura provides an Expo (React Native) SDK, which is hosted at https://github.com/criipto/criipto-verify-expo.
The Idura Verify Expo SDK allows your users to authenticate with a host of European eID providers. It allows your application to act as a public client, meaning it does not use a client secret, but instead employs PKCE to ensure that a malicious actor cannot intercept the authorization code.
In addition to the basic OIDC flow, the SDK also supports app switching.
minSdkVersion 26 or newer. Expo’s prebuild default is 24, so you must opt in explicitly via expo-build-properties — see Installation.use_frameworks! :linkage => :dynamic. The Idura Verify iOS SDK is distributed as a Swift Package, which requires dynamic framework linkage. The Expo plugin verifies both via expo-build-properties.npx expo run:ios / npx expo run:android or EAS Build.Both platforms delegate to the native Idura Verify SDKs (Android, iOS). Consumers on older Expo releases should stay on @criipto/verify-expo 3.x.
npm install @criipto/verify-expo expo-build-properties
The SDK needs to be configured with two pieces of information:
The SDK assumes that:
https://[YOUR IDURA DOMAIN]/[ios|android]/callback. Currently, custom redirect URLs are not supported. Open an issue if you need it.You should register the callback URLs in the Idura dashboard. If your domain is https://samples.criipto.id, you should register both https://samples.criipto.id/ios/callback and https://samples.criipto.id/android/callback.
Then configure both plugins in app.json. expo-build-properties must appear before @criipto/verify-expo so its overrides are applied first:
"plugins": [
["expo-build-properties", {
"android": { "minSdkVersion": 26 },
"ios": { "useFrameworks": "dynamic", "deploymentTarget": "17.4" }
}],
["@criipto/verify-expo", {
"domain": "YOUR_IDURA_DOMAIN",
"clientID": "urn:my:application:identifier:XXXX"
}]
]
The plugin fails expo prebuild with copy-paste-friendly errors if any of these requirements are unmet.
Call login() directly — there’s no provider or hook to wire up. Configuration (domain, clientID) lives in app.json and is read by the native modules. Hold the result in your own state (useState, zustand, react-query — your call) since the package doesn’t keep one for you:
// src/LoginButton.jsx
import { useState } from "react";
import { Button, Text } from "react-native";
import { login, OAuth2Error, UserCancelledError } from "@criipto/verify-expo";
export default function LoginButton() {
const [claims, setClaims] = useState(null);
const [error, setError] = useState(null);
const handlePress = async (acrValues) => {
try {
const { id_token, trace_id, claims } = await login({ acrValues });
setClaims(claims);
setError(null);
} catch (e) {
if (e instanceof UserCancelledError) return; // expected; not an error condition
setError(e);
setClaims(null);
}
};
return (
<>
<Button
onPress={() => handlePress("urn:grn:authn:dk:mitid:substantial")}
title="Login with Danish MitID"
/>
<Button
onPress={() => handlePress("urn:grn:authn:se:bankid:same-device")}
title="Login with Swedish BankID"
/>
<Button onPress={() => handlePress("urn:grn:authn:se:frejaid")} title="Login with Freja ID" />
<Button
onPress={() => handlePress("urn:grn:authn:no:bankid:substantial")}
title="Login with Norwegian BankID"
/>
{error ? <Text>An error occurred: {error.toString()}</Text> : null}
{claims ? <Text>{JSON.stringify(claims, null, 2)}</Text> : null}
</>
);
}
On failure login() throws a typed error: UserCancelledError for the user dismissing the flow, OAuth2Error for IdP errors, NoSuitableBrowserError (Android-only) when no Custom Tab-capable browser is installed, plus ModuleNotConfiguredError / UnknownPromptError / IduraVerifyInternalError. All carry the SDK’s trace_id where available, for correlation in the Idura dashboard.
The web view used to display the login page lets you choose between an ephemeral or a shared browser session.
The default value is to use an ephemeral session.
It is up to you to decide which flow is better, based on the UX requirements of your application. Not all eIDs require the user to enter anything in the webview — for example, Swedish BankID takes the user directly to their authenticator app. For this reason, you may also want to use a shared session for some eIDs but not others. Pass preferEphemeralSession on the login() call to override per-flow:
await login({
acrValues: "urn:grn:authn:dk:mitid:substantial",
preferEphemeralSession: false, // opt into a shared browser session
});
Danish MitID supports a re-authentication flow, so you do not need to rely on a shared browser session if you need to re-authenticate the user with MitID.
On Android the parameter is silently ignored: Custom Tab sessions don’t share cookies with the browser, and the Android SDK doesn’t expose the choice.
Learn more about Idura and sign up for your free developer account at idura.eu.