-
Notifications
You must be signed in to change notification settings - Fork 282
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Introduce Passkeys library for expo
In this commit, we are introducing a library to handle passkeys for Expo and Clerk. The library manages the creation and retrieval of passkeys for iOS, Android devices, and Expo web. We have also provided an example app that serves as a playground and helps us test the clerk-expo libraries. The README includes instructions on how to set up the app for iOS devices, and we will soon provide instructions for Android devices as well.
- Loading branch information
Showing
112 changed files
with
37,029 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
root: true, | ||
extends: ['universe/native', 'universe/web'], | ||
ignorePatterns: ['build'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# OSX | ||
# | ||
.DS_Store | ||
|
||
# VSCode | ||
.vscode/ | ||
jsconfig.json | ||
|
||
# Xcode | ||
# | ||
build/ | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
xcuserdata | ||
*.xccheckout | ||
*.moved-aside | ||
DerivedData | ||
*.hmap | ||
*.ipa | ||
*.xcuserstate | ||
project.xcworkspace | ||
|
||
# Android/IJ | ||
# | ||
.classpath | ||
.cxx | ||
.gradle | ||
.idea | ||
.project | ||
.settings | ||
local.properties | ||
android.iml | ||
android/app/libs | ||
android/keystores/debug.keystore | ||
|
||
# Cocoapods | ||
# | ||
example/ios/Pods | ||
|
||
# Ruby | ||
example/vendor/ | ||
|
||
# node.js | ||
# | ||
node_modules/ | ||
npm-debug.log | ||
yarn-debug.log | ||
yarn-error.log | ||
|
||
# Expo | ||
.expo/* | ||
.env | ||
.yalc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Exclude all top-level hidden directories by convention | ||
/.*/ | ||
|
||
# Exclude tarballs generated by `npm pack` | ||
/*.tgz | ||
|
||
__mocks__ | ||
__tests__ | ||
|
||
/babel.config.js | ||
/android/src/androidTest/ | ||
/android/src/test/ | ||
/android/build/ | ||
/example/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Running the Example Project with Clerk on Your iOS Simulator or Device | ||
|
||
## Steps | ||
|
||
### Before executing this steps you will need to have XCode installed and have an Apple Developer Account | ||
|
||
1. **Install module deps** | ||
Run `npm i` on the root of the project. | ||
|
||
2. **Navigate to the Example Folder** | ||
Open your terminal and move into the `example` folder of the project. | ||
|
||
3. **Set Up Environment Variables** | ||
Add the `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` to the `.env` file in the root of your project. | ||
|
||
4. **Configure Associated Domains** | ||
|
||
- Go to the iOS Application tab on the Clerk Dashboard. For testing purposes, it’s recommended to use a development instance. | ||
- Set up your associated domain file, which requires an Apple Developer account,You can find you App id prefix and Bundle ID at `https://developer.apple.com/account/resources/identifiers/list` | ||
- Replace the `associatedDomains` entry in `app.json` with the front end api provided by Clerk e.x `coyote-6.clerk.accounts.dev` . | ||
- Configure also the `bundleIdentifier` in `app.json` to match the bundle id that can be found at `https://developer.apple.com/account/resources/identifiers/list` | ||
|
||
5. **Install Dependencies** | ||
Run `npm install`. | ||
|
||
6. **Prebuild the Project** | ||
Run `npx expo prebuild --clean`. | ||
|
||
7. **Run the Project on iOS** | ||
Run `npm run ios` | ||
|
||
8. **How to use the example** | ||
|
||
- Navigate to Dashboard and enable the passkeys attribute at Email, Phone, Username page. | ||
- Navigate to dashboard and create a new user from the Users tab, use only email and password as authentication attributes. | ||
- Open your the device that you application is running and sign in with the user that you just created. | ||
- After signing in you will see a screen that has an option to create a Passkey, press that option and register your passkey. | ||
- Sign out | ||
- In the sign in screen select the option sign in with passkey |
53 changes: 53 additions & 0 deletions
53
packages/expo/modules/clerk-expo-passkeys/android/build.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
apply plugin: 'com.android.library' | ||
apply plugin: 'kotlin-android' | ||
apply plugin: 'maven-publish' | ||
|
||
group = 'expo.modules.clerkexpopasskeys' | ||
version = '0.1.0' | ||
|
||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") | ||
apply from: expoModulesCorePlugin | ||
applyKotlinExpoModulesCorePlugin() | ||
useCoreDependencies() | ||
useExpoPublishing() | ||
|
||
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true. | ||
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. | ||
// Most of the time, you may like to manage the Android SDK versions yourself. | ||
def useManagedAndroidSdkVersions = false | ||
if (useManagedAndroidSdkVersions) { | ||
useDefaultAndroidSdkVersions() | ||
} else { | ||
buildscript { | ||
// Simple helper that allows the root project to override versions declared by this library. | ||
ext.safeExtGet = { prop, fallback -> | ||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback | ||
} | ||
} | ||
project.android { | ||
compileSdkVersion safeExtGet("compileSdkVersion", 34) | ||
defaultConfig { | ||
minSdkVersion safeExtGet("minSdkVersion", 21) | ||
targetSdkVersion safeExtGet("targetSdkVersion", 35) | ||
} | ||
} | ||
} | ||
|
||
android { | ||
namespace "expo.modules.clerkexpopasskeys" | ||
defaultConfig { | ||
versionCode 1 | ||
versionName "0.1.0" | ||
} | ||
lintOptions { | ||
abortOnError false | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation project(':expo-modules-core') | ||
implementation("androidx.credentials:credentials:1.3.0-rc01") | ||
implementation("androidx.credentials:credentials-play-services-auth:1.3.0-rc01") | ||
|
||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/expo/modules/clerk-expo-passkeys/android/src/main/AndroidManifest.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<manifest> | ||
</manifest> |
140 changes: 140 additions & 0 deletions
140
...skeys/android/src/main/java/expo/modules/clerkexpopasskeys/ClerkExpoPasskeysExceptions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package expo.modules.clerkexpopasskeys | ||
|
||
import androidx.credentials.exceptions.CreateCredentialCancellationException | ||
import androidx.credentials.exceptions.CreateCredentialCustomException | ||
import androidx.credentials.exceptions.CreateCredentialException | ||
import androidx.credentials.exceptions.CreateCredentialInterruptedException | ||
import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException | ||
import androidx.credentials.exceptions.CreateCredentialUnknownException | ||
import androidx.credentials.exceptions.GetCredentialCancellationException | ||
import androidx.credentials.exceptions.GetCredentialException | ||
import androidx.credentials.exceptions.GetCredentialInterruptedException | ||
import androidx.credentials.exceptions.GetCredentialProviderConfigurationException | ||
import androidx.credentials.exceptions.GetCredentialUnknownException | ||
import androidx.credentials.exceptions.GetCredentialUnsupportedException | ||
import androidx.credentials.exceptions.NoCredentialException | ||
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException | ||
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException | ||
import expo.modules.kotlin.Promise | ||
|
||
//https://developer.android.com/identity/sign-in/credential-manager#create-passkey | ||
fun handleCreationFailure(e: CreateCredentialException, promise: Promise) { | ||
when (e) { | ||
is CreatePublicKeyCredentialDomException -> { | ||
// Handle the passkey DOM errors thrown according to the | ||
// WebAuthn spec. | ||
promise.reject("CreatePublicKeyCredentialDomException", e.domError.toString(), e) | ||
} | ||
|
||
is CreateCredentialCancellationException -> { | ||
// The user intentionally canceled the operation and chose not | ||
// to register the credential. | ||
promise.reject( | ||
"CreateCredentialCancellationException", e.message, | ||
e | ||
) | ||
|
||
|
||
} | ||
|
||
is CreateCredentialInterruptedException -> { | ||
// Retry-able error. Consider retrying the call. | ||
promise.reject( | ||
"CreateCredentialInterruptedException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
is CreateCredentialProviderConfigurationException -> { | ||
// Your app is missing the provider configuration dependency. | ||
// Most likely, you're missing the | ||
// "credentials-play-services-auth" module. | ||
promise.reject( | ||
"CreateCredentialProviderConfigurationException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
is CreateCredentialUnknownException -> { | ||
promise.reject( | ||
"CreateCredentialUnknownException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
is CreateCredentialCustomException -> { | ||
// You have encountered an error from a 3rd-party SDK. If you | ||
// make the API call with a request object that's a subclass of | ||
// CreateCustomCredentialRequest using a 3rd-party SDK, then you | ||
// should check for any custom exception type constants within | ||
// that SDK to match with e.type. Otherwise, drop or log the | ||
// exception. | ||
promise.reject( | ||
"CreateCredentialCustomException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
else -> promise.reject("Error", e.message, e) | ||
} | ||
} | ||
|
||
fun handleGetFailure(e: GetCredentialException, promise: Promise) { | ||
|
||
when (e) { | ||
is GetPublicKeyCredentialDomException -> { | ||
promise.reject("GetPublicKeyCredentialDomException", e.domError.toString(), e) | ||
} | ||
|
||
is GetCredentialInterruptedException -> { | ||
promise.reject("GetCredentialInterruptedException", e.message, e) | ||
} | ||
|
||
is GetCredentialCancellationException -> { | ||
promise.reject( | ||
"GetCredentialCancellationException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
is GetCredentialUnknownException -> { | ||
promise.reject("GetCredentialUnknownException", e.message, e) | ||
} | ||
|
||
|
||
is GetCredentialProviderConfigurationException -> { | ||
promise.reject( | ||
"GetCredentialProviderConfigurationException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
|
||
is GetCredentialUnsupportedException -> { | ||
promise.reject( | ||
"GetCredentialUnsupportedException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
is NoCredentialException -> { | ||
promise.reject( | ||
"NoCredentialException", | ||
e.message, | ||
e | ||
) | ||
} | ||
|
||
else -> { | ||
promise.reject("Error", e.message, e) | ||
} | ||
} | ||
|
||
} |
46 changes: 46 additions & 0 deletions
46
...-passkeys/android/src/main/java/expo/modules/clerkexpopasskeys/ClerkExpoPasskeysModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package expo.modules.clerkexpopasskeys | ||
|
||
import androidx.credentials.exceptions.CreateCredentialException | ||
import androidx.credentials.exceptions.GetCredentialException | ||
import expo.modules.kotlin.Promise | ||
import expo.modules.kotlin.modules.Module | ||
import expo.modules.kotlin.modules.ModuleDefinition | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.launch | ||
|
||
class ClerkExpoPasskeysModule : Module() { | ||
private val mCoroutine = CoroutineScope(Dispatchers.Default) | ||
|
||
override fun definition() = ModuleDefinition { | ||
Name("ClerkExpoPasskeys") | ||
|
||
AsyncFunction("create") { request: String, promise: Promise -> | ||
|
||
mCoroutine.launch { | ||
try { | ||
val response = createPasskey(request, appContext) | ||
promise.resolve(response) | ||
|
||
} catch (e: CreateCredentialException) { | ||
handleCreationFailure(e, promise) | ||
} | ||
} | ||
|
||
} | ||
|
||
AsyncFunction("get") { request: String, promise: Promise -> | ||
|
||
mCoroutine.launch { | ||
try { | ||
val response = getPasskey(request, appContext) | ||
promise.resolve(response) | ||
} catch (e: GetCredentialException) { | ||
handleGetFailure(e, promise) | ||
} | ||
} | ||
} | ||
|
||
|
||
} | ||
} |
Oops, something went wrong.