Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

A React Native wrapper for the Didit Identity Verification SDK. Supports both iOS and Android with a unified TypeScript API built on React Native's New Architecture (TurboModules).

Authentication is being prepared as the separate
[`@didit-protocol/auth-react-native`](packages/auth-react-native/README.md) package.
It is private and unavailable until the native DiditAuth integration is ready.
Authentication is the separate
[`@didit-protocol/auth-react-native`](packages/auth-react-native/README.md) package,
on native DiditAuth. It is not published yet. Both packages pin the same native
SDK release so that one app can use them together.

## Requirements

Expand Down Expand Up @@ -93,7 +94,7 @@ You do not need to request camera permission in your app code before calling `st

Workflows can include a **Bank** step (the person links a bank account) and a **Location** step (the device position is read once, with the person's consent). Both screens are drawn by the native SDKs, so this package needs no new API: they run inside `startVerification()` / `startVerificationWithWorkflow()`, and the outcome comes back as the same `VerificationResult`.

> **Development preview:** these steps currently use the native integration source pinned in `diditNativeSdkVersions.source` in `package.json`. Set `DIDIT_SDK_SOURCE_PATH` to a clean checkout at that revision before building either example. A missing or different checkout fails the build instead of substituting the 4.9.1 release, which does not contain these steps. The temporary source pin must move to the released native version before the stacked push and package release.
> **Development preview:** these steps currently use the native integration source pinned in `diditNativeSdkVersions.source` in `package.json`. Set `DIDIT_SDK_SOURCE_PATH` to a clean checkout at that revision before building either example. A missing or different checkout fails the build instead of substituting a released native SDK. The temporary source pin must move to the released native version before the stacked push and package release.

For Android source builds, apply this package's `android/native-source.gradle` from the host's `settings.gradle`. First run `yarn native:android` to build the pinned native revision with its own Gradle wrapper. The settings script exclusively resolves Didit artifacts from that local Maven repository, preventing a fallback to released artifacts. Both examples already do this; Expo prebuild adds it automatically.

Expand Down Expand Up @@ -249,7 +250,7 @@ The iOS podspec URL is derived from the native SDK version this package pins (`d
[
"@didit-protocol/sdk-react-native",
{
"iosPodspecUrl": "https://raw.githubusercontent.com/didit-protocol/sdk-ios/4.9.1/DiditSDK.podspec"
"iosPodspecUrl": "https://raw.githubusercontent.com/didit-protocol/sdk-ios/4.10.0/DiditSDK.podspec"
}
]
```
Expand Down Expand Up @@ -288,7 +289,7 @@ didit_sdk_subspec = case $DiditSdkIosVariant
else
raise "Invalid $DiditSdkIosVariant '#{$DiditSdkIosVariant}'. Supported values: all, core, autodetection, nfc."
end
pod didit_sdk_subspec, :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/4.9.1/DiditSDK.podspec'
pod didit_sdk_subspec, :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/4.10.0/DiditSDK.podspec'
```

##### Swift Package Manager (optional)
Expand Down
2 changes: 2 additions & 0 deletions build-native-android.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const modules = [
'sdk-autodetection',
'sdk-nfc',
'sdk-wallet',
'sdk-signals',
'sdk-auth',
];
const args = [
...modules.map(
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"name": "@didit-protocol/sdk-react-native",
"version": "4.9.1",
"diditNativeSdkVersions": {
"ios": "4.9.1",
"android": "4.9.1",
"ios": "4.10.0",
"android": "4.10.0",
"source": {
"branch": "integration/console-v2",
"revision": "88259c3b2ac57520ead46579ee7c1fb56686cdb1"
"branch": "main",
"revision": "cda7fe301fbda606d4256cdd77baf2e850baba17"
}
},
"description": "React Native wrapper for Didit Identity Verification SDK",
Expand Down
51 changes: 48 additions & 3 deletions packages/auth-react-native/DiditAuthReactNative.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,63 @@ require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

# Single source of truth for the native iOS version: the same release as the
# verification package's, so both can share one app.
didit_sdk_ios_version = package.dig("diditNativeSdkVersions", "ios").to_s
if didit_sdk_ios_version.empty?
raise "Missing 'diditNativeSdkVersions.ios' in package.json - cannot pin DiditSDK/Auth."
end

# "cocoapods" (default) depends on the DiditSDK/Auth subspec, whose podspec the
# host declares; "spm" adds the DiditAuth product of the sdk-ios package. One
# setting for both Didit packages, as in SdkReactNative.podspec.
didit_sdk_ios_linkage = (
defined?($DiditSdkIosLinkage) && $DiditSdkIosLinkage ?
$DiditSdkIosLinkage.to_s :
ENV.fetch("DIDIT_SDK_IOS_LINKAGE", "cocoapods")
).downcase

unless ["spm", "cocoapods"].include?(didit_sdk_ios_linkage)
raise "Invalid DiditSdk iOS linkage '#{didit_sdk_ios_linkage}'. Set $DiditSdkIosLinkage or DIDIT_SDK_IOS_LINKAGE to one of: spm, cocoapods."
end
if didit_sdk_ios_linkage == "spm" && !defined?(spm_dependency)
raise "DiditSdk iOS linkage 'spm' requires React Native 0.75 or newer (spm_dependency is unavailable). Use the 'cocoapods' linkage instead."
end

Pod::Spec.new do |s|
s.name = "DiditAuthReactNative"
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.authors = "Didit"
s.platforms = { :ios => min_ios_version_supported }
# DiditAuth needs iOS 15; its hosted pages need 17.4 at run time.
s.platforms = { :ios => [min_ios_version_supported, "15.0"].max_by { |version| Gem::Version.new(version) } }
s.source = { :git => "https://github.com/didit-protocol/sdk-react-native.git", :tag => "auth-react-native-v#{s.version}" }
s.source_files = "ios/**/*.{h,mm}"
s.source_files = "ios/**/*.{h,mm,swift}"
s.private_header_files = "ios/**/*.h"

# No DiditAuth dependency until a reviewed native artifact is available.
s.static_framework = true
s.swift_version = "5.0"

s.pod_target_xcconfig = {
"FRAMEWORK_SEARCH_PATHS" => [
'$(inherited)',
'"${PODS_CONFIGURATION_BUILD_DIR}/DiditSDK"',
'"${PODS_XCFRAMEWORKS_BUILD_DIR}/DiditSDK"',
'"${PODS_XCFRAMEWORKS_BUILD_DIR}/DiditSDK/Auth"'
].join(" ")
}

if didit_sdk_ios_linkage == "spm"
spm_dependency(s,
url: "https://github.com/didit-protocol/sdk-ios.git",
requirement: { kind: "exactVersion", version: didit_sdk_ios_version },
products: ["DiditAuth"]
)
else
s.dependency "DiditSDK/Auth", didit_sdk_ios_version
end

install_modules_dependencies(s)
end
219 changes: 148 additions & 71 deletions packages/auth-react-native/README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,152 @@
# DiditAuth for React Native

**Not ready for authentication or publication.** This private workspace prepares
`@didit-protocol/auth-react-native` as an independent native module. Both native
implementations currently reject every operation. There is no released native
DiditAuth dependency or reviewed initialization/result contract to connect yet.

## Current behavior

The named exports `signIn`, `currentSession`, `getToken`, `signOut` and `stepUp`
take no arguments and return `Promise<never>` in this unavailable-only build.
They reserve the native method names, not the final integration API. Configuration,
options, successful session/token types and cancellation semantics must be added
with the reviewed native contract before publication.

Importing the package without a linked native module is safe. Calling an operation
rejects with `code: 'ERR_DIDIT_AUTH_UNAVAILABLE'`. The native placeholders use the
same code as the JavaScript `DiditAuthUnavailableError`. Match the `code` across
the native boundary; native errors are not instances of the JavaScript class.
Other native rejections propagate unchanged. An unexpected native resolution also
rejects as unavailable, so this build never reports a successful authentication,
signed-out session, token retrieval, logout or step-up.

No credentials are accepted, stored, logged or sent. The package has no dependency
on the verification wrapper or its camera, NFC and location features. It registers
its own `DiditAuthReactNative` TurboModule and `DiditAuthReactNativeSpec` codegen
library. It adds no screens, network calls, URL schemes or OS permissions.

## Local integration

Use React Native's New Architecture. Codegen is checked with React Native 0.77 and
the repository's 0.83 version; platform support remains subject to native builds
and the eventual DiditAuth dependency's minimum versions. Expo Go cannot load the
native module; a development build will be needed for a real native integration.

From the repository root:

```sh
yarn install --immutable
yarn build:auth
yarn workspace @didit-protocol/auth-react-native pack --out /tmp/didit-auth-react-native.tgz
`@didit-protocol/auth-react-native` signs your app's end users up and in with Didit
Authentication. It calls native DiditAuth (`me.didit:didit-auth` on Android,
`DiditSDK/Auth` on iOS): your application's hosted Didit page with PKCE, passkeys,
sessions in the Keychain or Android Keystore, sign-out and step-ups before
sensitive actions. Every request carries the device signals of DiditSignals.

**Not published yet.** The package is private and pins native SDK 4.10.0
(`diditNativeSdkVersions` in its `package.json`), the first release with DiditAuth.

## Requirements

- React Native 0.77 or later with the New Architecture. Expo Go cannot load the
native module; use a development build.
- iOS 15 or later; the hosted pages (sign-in and step-up checks) need iOS 17.4.
Below 17.4 those calls fail with `browser_unavailable`.
- Android: passkeys need Android 9 or later.

## Set up the redirect

Didit sends the person back to your app with an HTTPS redirect; it accepts no
custom URL schemes.

1. In the Didit console (Authentication, Developers), add the redirect URI, for
example `https://app.example.com/didit/callback`, and copy the publishable key.
2. Android: make that address an App Link of your app and declare DiditAuth's
redirect activity with it in `android/app/src/main/AndroidManifest.xml` (add
`xmlns:tools="http://schemas.android.com/tools"` to the manifest element):

```xml
<activity
android:name="me.didit.sdk.auth.DiditAuthRedirectActivity"
android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:node="replace">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="app.example.com" android:path="/didit/callback" />
</intent-filter>
</activity>
```

Publish `https://app.example.com/.well-known/assetlinks.json` for your package
and signing certificate.
3. iOS: add `webcredentials:app.example.com` to the app's Associated Domains and
serve `https://app.example.com/.well-known/apple-app-site-association` listing
your app under `webcredentials`.
4. Passkeys: add your Android package and certificate fingerprints, and your iOS
app ID, under Native apps in the console.

The native SDK guides have the details: [Android](https://github.com/didit-protocol/sdk-android)
and [iOS](https://github.com/didit-protocol/sdk-ios).

## Install the native SDK

Android resolves `me.didit:didit-auth` from the Didit Maven repository. Add it to the
project-level `settings.gradle` (the verification package needs the same entry):

```groovy
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository"
content { includeGroup "me.didit" }
}
}
}
```

On iOS, CocoaPods is the default. DiditSDK is not on the CocoaPods trunk, so the
Podfile names its podspec at the pinned version:

```ruby
pod 'DiditSDK/Auth', :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/4.10.0/DiditSDK.podspec'
```

To resolve it with Swift Package Manager instead, set `$DiditSdkIosLinkage = 'spm'`
before `use_native_modules!` and leave the `pod` line out. The package then adds the
`DiditAuth` product of `sdk-ios`.

## Use

```ts
import { createDiditAuth, DiditAuthError } from '@didit-protocol/auth-react-native';

// One per application; reuse it.
const auth = createDiditAuth({
publishableKey: 'pk_live_...',
redirectUri: 'https://app.example.com/didit/callback',
});

const session = await auth.signIn(); // hosted page: sign-up and sign-in
await auth.signInWithPasskey(); // a passkey of this device
const passkeyId = await auth.registerPasskey(); // needs a session at most five minutes old

await auth.currentSession(); // DiditSession or null, cached for 30 seconds
await auth.currentSession({ force: true }); // asks Didit now
const token = await auth.getToken(); // five-minute token for your backend, or null
await auth.signOut();

const result = await auth.stepUp({ action: 'payout', amount: '900.00', currency: 'EUR' });
if (result.approved) {
// Your backend confirms with GET step-up/{result.id}/ before it pays out.
}
```

`stepUp` also takes `{ action: 'bank' }` (adding or changing a bank account) and
`{ action: 'contact' }` (changing the email or phone). The result's `status` is
`not_required`, `approved`, `pending`, `review`, `declined` or `expired`.

A `DiditSession` has `userId`, `sessionId`, `applicationId`, `vendorData`, `email`,
`emailVerified`, `phone`, `methods`, `mfaVerified`, `verified`, `organizations` and
`expiresAt`. Verify the access token on your backend against the issuer's JWKS.
Refresh tokens stay in native storage and never reach JavaScript.

## Errors

Every call rejects with a `DiditAuthError` carrying native DiditAuth's `code` and
`message` unchanged, plus `status`, `fields` and `retryAfter` when Didit sent them:

- the Didit API's own codes, such as `invalid_grant`, `method_unavailable` or
`reauthentication_required`;
- `cancelled`, `invalid_state`, `not_signed_in`, `passkey_cancelled`,
`passkey_unavailable`, `browser_unavailable`, `storage_unavailable`,
`invalid_publishable_key`, `invalid_redirect_uri`, `network_error`,
`rate_limited` (see `retryAfter`) and `server_error`.

```ts
try {
await auth.signIn();
} catch (error) {
if (error instanceof DiditAuthError && error.code === 'cancelled') return;
throw error;
}
```

The archive can be installed in a disposable React Native application for
autolinking checks, either alone or alongside the verification package. `private:
true` blocks registry publication. The root verification release does not include
this workspace. This package uses the existing Bob toolchain for CommonJS, ESM,
TypeScript declarations and React Native source exports.

## Completing the native integration

Before enabling or publishing the package:

1. Connect the reviewed native DiditAuth initialization and method contracts.
Replace the rejecting native implementations and the JavaScript success gate
together. Define typed session results, options and distinct cancellation,
signed-out, expired/revoked and failure outcomes from that contract.
2. Add real distribution coordinates with native versions single-sourced through
`diditNativeSdkVersions`; never borrow the verification package's pins.
3. Keep PKCE/state validation, callback ownership, passkeys, secure token storage,
serialized refresh, logout/revocation and automatic signals in native DiditAuth.
Treat access tokens as opaque values. Never expose refresh tokens to JavaScript.
4. Document and test the native callback/associated-domain setup against the
supported backend contract before adding an Expo configuration plugin. Verify
app/environment isolation, process restoration and callback replay rejection.
5. Run real native sign-in, cancel, session restore/expiry/revocation, concurrent
refresh, sign-out and step-up tests. Capture the launched hosted/native flows
in both themes and required sizes. iOS compilation and device/passkey checks
require a Mac. Codegen and package tests alone do not prove native runtime or
authentication readiness.

The matching Flutter `didit_auth` requires its own package in `sdk-flutter`, with
the same native dependency and acceptance checks. It is not included in this
React Native workspace. Hosted pages, backend contracts and native DiditAuth
remain separate dependencies; this package does not replace their implementations.
Calls also fail with `browser_unavailable` when no screen of the app is in the
foreground. In a build without the native module, such as Expo Go, importing the
package works and every call rejects with `DiditAuthUnavailableError` (code
`ERR_DIDIT_AUTH_UNAVAILABLE`).

## With the verification package

`@didit-protocol/sdk-react-native` and this package pin the same native release, so
both can be installed in one app. On iOS the verification SDK's `DiditSDK` binary
also contains DiditAuth; with both packages the app ships a second copy of
DiditAuth and DiditSignals in the `DiditAuth` binary.
18 changes: 16 additions & 2 deletions packages/auth-react-native/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ if (project.extensions.findByName('kotlin') == null) {
}
apply plugin: "com.facebook.react"

def nativePins = new groovy.json.JsonSlurper().parse(file("../package.json")).diditNativeSdkVersions

android {
namespace "me.didit.authreactnative"
compileSdkVersion authExtOrDefault("compileSdkVersion", 36)
Expand All @@ -39,8 +41,20 @@ android {
}
}

repositories {
// Didit SDK Maven repository, scoped to Didit's group like the verification
// package's: unfiltered, Gradle would probe it for every dependency.
maven {
url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository"
content { includeGroup "me.didit" }
}
}

dependencies {
implementation "com.facebook.react:react-android"
// No DiditAuth artifact is released yet. Keep this unavailable-only module
// independent of verification and add real pins only after native review.

// Didit Authentication; it brings didit-signals only, not the verification SDK.
implementation "me.didit:didit-auth:${nativePins.android}"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
}
Loading
Loading