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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,5 @@ gen-external-apklibs
version.txt

# Internal planning docs
plans/
plans/
docs/
121 changes: 121 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
- [Passwordless Login](#passwordless-login)
- [Step 1: Request the code](#step-1-request-the-code)
- [Step 2: Input the code](#step-2-input-the-code)
- [Passwordless Login with a Database Connection (EA)](#passwordless-login-with-a-database-connection-ea)
- [Step 1: Issue an OTP challenge](#step-1-issue-an-otp-challenge)
- [Step 2: Verify the code and log in](#step-2-verify-the-code-and-log-in)
- [Sign Up with a database connection](#sign-up-with-a-database-connection)
- [Get user information](#get-user-information)
- [Custom Token Exchange](#custom-token-exchange)
Expand Down Expand Up @@ -1488,6 +1491,124 @@ authentication

> The default scope used is `openid profile email`. Regardless of the scopes set to the request, the `openid` scope is always enforced.

### Passwordless Login with a Database Connection (EA)

> [!IMPORTANT]
> Passwordless Login for database connections is currently in [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). Please reach out to Auth0 support to get it enabled for your tenant.

This flow lets users authenticate with a one-time code sent over email or SMS/voice against a **database connection** that has `email_otp` or `phone_otp` enabled. It is distinct from the `/passwordless/start` flow described above, which uses dedicated passwordless connections.

Obtain a `PasswordlessClient` from the `AuthenticationAPIClient`:

```kotlin
val passwordless = AuthenticationAPIClient(account).passwordlessClient()
```

The flow has two steps: first issue an OTP challenge, then — after the user enters the code they received — exchange it for credentials. **Save the `PasswordlessChallenge` from step 1**, as you pass that same object into `loginWithOTP` in step 2.

#### Step 1: Issue an OTP challenge

Send a one-time code to the user's email. For privacy, the server **always responds successfully regardless of whether the user exists**. On success, save the returned `PasswordlessChallenge` for step 2.

```kotlin
// keep this reference until the user enters the code
var challenge: PasswordlessChallenge? = null

passwordless
.challengeWithEmail("info@auth0.com", "my-database-connection")
.start(object: Callback<PasswordlessChallenge, AuthenticationException> {
override fun onFailure(exception: AuthenticationException) { }

override fun onSuccess(result: PasswordlessChallenge) {
challenge = result
}
})
```

To send the code over SMS or voice instead, use `challengeWithPhoneNumber` against a connection with `phone_otp` enabled, choosing the `DeliveryMethod`:

```kotlin
passwordless
.challengeWithPhoneNumber("+15555550123", "my-database-connection", DeliveryMethod.TEXT)
.start(object: Callback<PasswordlessChallenge, AuthenticationException> {
override fun onFailure(exception: AuthenticationException) { }

override fun onSuccess(result: PasswordlessChallenge) {
challenge = result
}
})
```

Both challenge methods accept an optional `allowSignup` parameter (defaults to `false`) that controls whether a new user is created if one does not yet exist.

#### Step 2: Verify the code and log in

Once the user enters the code, pass the saved `challenge` together with that code to `loginWithOTP` to obtain `Credentials`. If DPoP is enabled on the originating `AuthenticationAPIClient`, a DPoP proof is attached automatically to this token request.

```kotlin
passwordless
.loginWithOTP(challenge, "123456")
.start(object: Callback<Credentials, AuthenticationException> {
override fun onFailure(exception: AuthenticationException) { }

override fun onSuccess(credentials: Credentials) { }
})
```

<details>
<summary>Using coroutines</summary>

```kotlin
// Step 1: issue the challenge and keep it
val challenge = passwordless
.challengeWithEmail("info@auth0.com", "my-database-connection")
.await()

// Step 2: once the user enters the code, pass the saved challenge back to log in
val credentials = passwordless
.loginWithOTP(challenge, "123456")
.await()
```
</details>

<details>
<summary>Using Java</summary>

```java
// Step 1: issue the challenge and keep it
passwordless
.challengeWithEmail("info@auth0.com", "my-database-connection", false)
.start(new Callback<PasswordlessChallenge, AuthenticationException>() {
@Override
public void onSuccess(PasswordlessChallenge result) {
challenge = result;
}

@Override
public void onFailure(@NonNull AuthenticationException error) {
//Error!
}
});

// Step 2: once the user enters the code, pass the saved challenge back to log in
passwordless
.loginWithOTP(challenge, "123456")
.start(new Callback<Credentials, AuthenticationException>() {
@Override
public void onSuccess(@Nullable Credentials payload) {
//Logged in!
}

@Override
public void onFailure(@NonNull AuthenticationException error) {
//Error!
}
});
```
</details>

> The default scope used is `openid profile email`. Regardless of the scopes set to the request, the `openid` scope is always enforced.

### Sign Up with a database connection

```kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.auth0.android.Auth0
import com.auth0.android.Auth0Exception
import com.auth0.android.NetworkErrorException
import com.auth0.android.authentication.mfa.MfaApiClient
import com.auth0.android.authentication.passwordless.PasswordlessClient
import com.auth0.android.authentication.request.ActorToken
import com.auth0.android.dpop.DPoP
import com.auth0.android.dpop.DPoPException
Expand Down Expand Up @@ -118,6 +119,27 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
return MfaApiClient(this.auth0, mfaToken)
}

/**
* Creates a [PasswordlessClient] for the database-connection passwordless flow.
*
* ## Availability
*
* This feature is currently available in
* [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access).
* Please reach out to Auth0 support to get it enabled for your tenant.
*
* ## Usage
*
* ```kotlin
* val passwordless = authClient.passwordlessClient()
* ```
*
* @return a new [PasswordlessClient] instance bound to this client's Auth0 account.
*/
public fun passwordlessClient(): PasswordlessClient {
return PasswordlessClient(this.auth0, gson, this.dPoP)
}

/**
* Log in a user with email/username and password for a connection/realm.
* It will use the password-realm grant type for the `/oauth/token` endpoint
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.auth0.android.authentication.passwordless

/**
* Delivery method for a phone-number OTP challenge.
*
* Maps to the `delivery_method` request parameter of `POST /otp/challenge`. [TEXT] sends the
* one-time code via SMS (the server default); [VOICE] delivers it through a voice call.
*
* @property value the wire value sent to the server.
*/
public enum class DeliveryMethod(public val value: String) {
/** Deliver the one-time code via SMS. */
TEXT("text"),

/** Deliver the one-time code via a voice call. */
VOICE("voice")
}
Loading
Loading