Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ jobs:
- name: Build package
run: yarn prepare

- name: Build authentication package
run: yarn build:auth

- name: Check authentication package contents
run: yarn workspace @didit-protocol/auth-react-native pack --dry-run

build-android:
name: Released Android compatibility
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +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.

## Requirements

- React Native 0.76+ (New Architecture / TurboModules)
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ export default defineConfig([
},
},
{
ignores: ['node_modules/', 'lib/'],
ignores: ['node_modules/', 'lib/', 'packages/*/lib/'],
},
]);
1 change: 1 addition & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@didit-protocol/auth-react-native": "workspace:*",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"release": "release-it --only-version",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"test": "jest",
"native:android": "node build-native-android.js"
"native:android": "node build-native-android.js",
"build:auth": "yarn workspace @didit-protocol/auth-react-native build"
},
"keywords": [
"react-native",
Expand Down Expand Up @@ -118,7 +119,8 @@
},
"workspaces": [
"example",
"example-expo"
"example-expo",
"packages/auth-react-native"
],
"packageManager": "yarn@4.11.0",
"react-native-builder-bob": {
Expand Down Expand Up @@ -183,7 +185,9 @@
"preset": "react-native",
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
"<rootDir>/lib/",
"<rootDir>/packages/auth-react-native/lib/",
"<rootDir>/packages/auth-react-native/node_modules/"
]
},
"expo": {
Expand Down
19 changes: 19 additions & 0 deletions packages/auth-react-native/DiditAuthReactNative.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require "json"

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

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 }
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.private_header_files = "ios/**/*.h"

# No DiditAuth dependency until a reviewed native artifact is available.
install_modules_dependencies(s)
end
20 changes: 20 additions & 0 deletions packages/auth-react-native/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
MIT License

Copyright (c) 2026 https://didit.me
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
75 changes: 75 additions & 0 deletions packages/auth-react-native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# 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
```

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.
46 changes: 46 additions & 0 deletions packages/auth-react-native/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
buildscript {
ext.authExtOrDefault = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:8.7.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${authExtOrDefault('kotlinVersion', '2.0.21')}"
}
}

apply plugin: "com.android.library"
if (project.extensions.findByName('kotlin') == null) {
apply plugin: "kotlin-android"
}
apply plugin: "com.facebook.react"

android {
namespace "me.didit.authreactnative"
compileSdkVersion authExtOrDefault("compileSdkVersion", 36)

defaultConfig {
minSdkVersion authExtOrDefault("minSdkVersion", 24)
targetSdkVersion authExtOrDefault("targetSdkVersion", 36)
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}
}

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.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package me.didit.authreactnative

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule

/** Native auth remains unavailable until its reviewed implementation is linked. */
@ReactModule(name = DiditAuthModule.NAME)
class DiditAuthModule(context: ReactApplicationContext) : NativeDiditAuthSpec(context) {
override fun getName() = NAME

override fun signIn(promise: Promise) = unavailable(promise)
override fun currentSession(promise: Promise) = unavailable(promise)
override fun getToken(promise: Promise) = unavailable(promise)
override fun signOut(promise: Promise) = unavailable(promise)
override fun stepUp(promise: Promise) = unavailable(promise)

private fun unavailable(promise: Promise) {
promise.reject("ERR_DIDIT_AUTH_UNAVAILABLE", "DiditAuth is unavailable in this build.")
}

companion object {
const val NAME = "DiditAuthReactNative"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package me.didit.authreactnative

import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider

class DiditAuthPackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
if (name == DiditAuthModule.NAME) DiditAuthModule(reactContext) else null

override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
val constructors = ReactModuleInfo::class.java.constructors
val sevenParamCtor = constructors.firstOrNull { it.parameterTypes.size == 7 }
val info = if (sevenParamCtor != null) {
// RN 0.77-0.78 includes hasConstants; newer versions may retain this overload.
sevenParamCtor.newInstance(
DiditAuthModule.NAME,
DiditAuthModule::class.java.name,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // hasConstants
false, // isCxxModule
true // isTurboModule
)
} else {
// RN 0.79+ also supports registration without hasConstants.
constructors.first { it.parameterTypes.size == 6 }.newInstance(
DiditAuthModule.NAME,
DiditAuthModule::class.java.name,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
)
} as ReactModuleInfo
mapOf(DiditAuthModule.NAME to info)
}
}
4 changes: 4 additions & 0 deletions packages/auth-react-native/ios/DiditAuthReactNative.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#import <DiditAuthReactNativeSpec/DiditAuthReactNativeSpec.h>

@interface DiditAuthReactNative : NSObject <NativeDiditAuthSpec>
@end
48 changes: 48 additions & 0 deletions packages/auth-react-native/ios/DiditAuthReactNative.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#import "DiditAuthReactNative.h"

@implementation DiditAuthReactNative

RCT_EXPORT_MODULE(DiditAuthReactNative)

+ (BOOL)requiresMainQueueSetup
{
return NO;
}

- (void)rejectUnavailable:(RCTPromiseRejectBlock)reject
{
reject(@"ERR_DIDIT_AUTH_UNAVAILABLE", @"DiditAuth is unavailable in this build.", nil);
}

- (void)signIn:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
[self rejectUnavailable:reject];
}

- (void)currentSession:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
[self rejectUnavailable:reject];
}

- (void)getToken:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
[self rejectUnavailable:reject];
}

- (void)signOut:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
[self rejectUnavailable:reject];
}

- (void)stepUp:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
[self rejectUnavailable:reject];
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeDiditAuthSpecJSI>(params);
}

@end
Loading
Loading