-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added support for passwordless authentication (#503)
- Loading branch information
Showing
27 changed files
with
759 additions
and
0 deletions.
There are no files selected for viewing
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 |
---|---|---|
|
@@ -25,6 +25,7 @@ | |
- [📱 Authentication API](#-authentication-api) | ||
- [Login with database connection](#login-with-database-connection) | ||
- [Sign up with database connection](#sign-up-with-database-connection) | ||
- [Passwordless Login](#passwordless-login) | ||
- [Retrieve user information](#retrieve-user-information) | ||
- [Renew credentials](#renew-credentials) | ||
- [Errors](#errors-2) | ||
|
@@ -586,6 +587,47 @@ final databaseUser = await auth0.api.signup( | |
|
||
> 💡 You might want to log the user in after signup. See [Login with database connection](#login-with-database-connection) above for an example. | ||
### Passwordless Login | ||
Passwordless is a two-step authentication flow that requires the **Passwordless OTP** grant to be enabled for your Auth0 application. Check [our documentation](https://auth0.com/docs/get-started/applications/application-grant-types) for more information. | ||
|
||
#### 1. Start the passwordless flow | ||
|
||
Request a code to be sent to the user's email or phone number. For email scenarios, a link can be sent in place of the code. | ||
|
||
```dart | ||
await auth0.api.startPasswordlessWithEmail( | ||
email: "[email protected]", passwordlessType: PasswordlessType.code); | ||
``` | ||
<details> | ||
<summary>Using PhoneNumber</summary> | ||
|
||
```dart | ||
await auth0.api.startPasswordlessWithPhoneNumber( | ||
phoneNumber: "123456789", passwordlessType: PasswordlessType.code); | ||
``` | ||
</details> | ||
|
||
#### 2. Login with the received code | ||
|
||
To complete the authentication, you must send back that code the user received along with the email or phone number used to start the flow. | ||
|
||
```dart | ||
final credentials = await auth0.api.loginWithEmailCode( | ||
email: "[email protected]", verificationCode: "000000"); | ||
``` | ||
|
||
<details> | ||
<summary>Using SMS</summary> | ||
|
||
```dart | ||
final credentials = await auth0.api.loginWithSmsCode( | ||
phoneNumber: "123456789", verificationCode: "000000"); | ||
``` | ||
</details> | ||
|
||
> [!NOTE] | ||
> Sending additional parameters is supported only on iOS at the moment. | ||
### Retrieve user information | ||
|
||
Fetch the latest user information from the `/userinfo` endpoint. | ||
|
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
59 changes: 59 additions & 0 deletions
59
...kotlin/com/auth0/auth0_flutter/request_handlers/api/EmailPasswordlessApiRequestHandler.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,59 @@ | ||
package com.auth0.auth0_flutter.request_handlers.api | ||
|
||
import com.auth0.android.authentication.AuthenticationAPIClient | ||
import com.auth0.android.authentication.AuthenticationException | ||
import com.auth0.android.authentication.PasswordlessType | ||
import com.auth0.android.callback.Callback | ||
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest | ||
import com.auth0.auth0_flutter.toMap | ||
import com.auth0.auth0_flutter.utils.assertHasProperties | ||
import io.flutter.plugin.common.MethodChannel | ||
import java.util.HashMap | ||
|
||
private const val PASSWORDLESS_EMAIL_LOGIN_METHOD = "auth#passwordlessWithEmail" | ||
|
||
class EmailPasswordlessApiRequestHandler : ApiRequestHandler { | ||
override val method: String = PASSWORDLESS_EMAIL_LOGIN_METHOD | ||
|
||
override fun handle( | ||
api: AuthenticationAPIClient, | ||
request: MethodCallRequest, | ||
result: MethodChannel.Result | ||
) { | ||
val args = request.data | ||
assertHasProperties(listOf("email", "passwordlessType"), args) | ||
val passwordlessType = getPasswordlessType(args["passwordlessType"] as String) | ||
|
||
val builder = api.passwordlessWithEmail( | ||
args["email"] as String, | ||
passwordlessType | ||
) | ||
|
||
if (args["parameters"] is HashMap<*, *>) { | ||
builder.addParameters(args["parameters"] as Map<String, String>) | ||
} | ||
|
||
builder.start(object : Callback<Void?, AuthenticationException> { | ||
override fun onFailure(error: AuthenticationException) { | ||
result.error( | ||
error.getCode(), | ||
error.getDescription(), | ||
error.toMap() | ||
) | ||
} | ||
|
||
override fun onSuccess(void: Void?) { | ||
result.success(null) | ||
} | ||
}) | ||
} | ||
|
||
private fun getPasswordlessType(passwordlessType: String): PasswordlessType { | ||
return when (passwordlessType) { | ||
"code" -> PasswordlessType.CODE | ||
"link" -> PasswordlessType.WEB_LINK | ||
"link_android" -> PasswordlessType.ANDROID_LINK | ||
else -> PasswordlessType.CODE | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
...otlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithEmailCodeApiRequestHandler.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,67 @@ | ||
package com.auth0.auth0_flutter.request_handlers.api | ||
|
||
import com.auth0.android.authentication.AuthenticationAPIClient | ||
import com.auth0.android.authentication.AuthenticationException | ||
import com.auth0.android.callback.Callback | ||
import com.auth0.android.result.Credentials | ||
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest | ||
import com.auth0.auth0_flutter.toMap | ||
import com.auth0.auth0_flutter.utils.assertHasProperties | ||
import io.flutter.plugin.common.MethodChannel | ||
import java.util.ArrayList | ||
import java.util.HashMap | ||
|
||
private const val EMAIL_LOGIN_METHOD = "auth#loginWithEmail" | ||
|
||
class LoginWithEmailCodeApiRequestHandler : ApiRequestHandler { | ||
override val method: String = EMAIL_LOGIN_METHOD | ||
|
||
override fun handle( | ||
api: AuthenticationAPIClient, | ||
request: MethodCallRequest, | ||
result: MethodChannel.Result | ||
) { | ||
val args = request.data | ||
assertHasProperties(listOf("email", "verificationCode"), args) | ||
|
||
val builder = api.loginWithEmail( | ||
args["email"] as String, | ||
args["verificationCode"] as String | ||
).apply { | ||
val scopes = (args["scopes"] ?: arrayListOf<String>()) as ArrayList<*> | ||
setScope(scopes.joinToString(separator = " ")) | ||
if (args["audience"] is String) { | ||
setAudience(args["audience"] as String) | ||
} | ||
if (args["parameters"] is HashMap<*, *>) { | ||
addParameters(args["parameters"] as Map<String, String>) | ||
} | ||
} | ||
|
||
builder.start(object : Callback<Credentials, AuthenticationException> { | ||
override fun onFailure(error: AuthenticationException) { | ||
result.error( | ||
error.getCode(), | ||
error.getDescription(), | ||
error.toMap() | ||
) | ||
} | ||
|
||
override fun onSuccess(credentials: Credentials) { | ||
val scope = credentials.scope?.split(" ") ?: listOf() | ||
val formattedDate = credentials.expiresAt.toInstant().toString() | ||
result.success( | ||
mapOf( | ||
"accessToken" to credentials.accessToken, | ||
"idToken" to credentials.idToken, | ||
"refreshToken" to credentials.refreshToken, | ||
"userProfile" to credentials.user.toMap(), | ||
"expiresAt" to formattedDate, | ||
"scopes" to scope, | ||
"tokenType" to credentials.type | ||
) | ||
) | ||
} | ||
}) | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
.../kotlin/com/auth0/auth0_flutter/request_handlers/api/LoginWithSMSCodeApiRequestHandler.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,63 @@ | ||
package com.auth0.auth0_flutter.request_handlers.api | ||
|
||
import com.auth0.android.authentication.AuthenticationAPIClient | ||
import com.auth0.android.authentication.AuthenticationException | ||
import com.auth0.android.callback.Callback | ||
import com.auth0.android.result.Credentials | ||
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest | ||
import com.auth0.auth0_flutter.toMap | ||
import com.auth0.auth0_flutter.utils.assertHasProperties | ||
import io.flutter.plugin.common.MethodChannel | ||
import java.util.ArrayList | ||
import java.util.HashMap | ||
|
||
private const val SMS_LOGIN_METHOD = "auth#loginWithPhoneNumber" | ||
|
||
class LoginWithSMSCodeApiRequestHandler : ApiRequestHandler { | ||
override val method: String = SMS_LOGIN_METHOD | ||
|
||
override fun handle( | ||
api: AuthenticationAPIClient, request: MethodCallRequest, result: MethodChannel.Result | ||
) { | ||
val args = request.data | ||
assertHasProperties(listOf("phoneNumber", "verificationCode"), args) | ||
|
||
val builder = api.loginWithPhoneNumber( | ||
args["email"] as String, | ||
args["verificationCode"] as String | ||
).apply { | ||
val scopes = (args["scopes"] ?: arrayListOf<String>()) as ArrayList<*> | ||
setScope(scopes.joinToString(separator = " ")) | ||
if (args["audience"] is String) { | ||
setAudience(args["audience"] as String) | ||
} | ||
if (args["parameters"] is HashMap<*, *>) { | ||
addParameters(args["parameters"] as Map<String, String>) | ||
} | ||
} | ||
|
||
builder.start(object : Callback<Credentials, AuthenticationException> { | ||
override fun onFailure(error: AuthenticationException) { | ||
result.error( | ||
error.getCode(), error.getDescription(), error.toMap() | ||
) | ||
} | ||
|
||
override fun onSuccess(credentials: Credentials) { | ||
val scope = credentials.scope?.split(" ") ?: listOf() | ||
val formattedDate = credentials.expiresAt.toInstant().toString() | ||
result.success( | ||
mapOf( | ||
"accessToken" to credentials.accessToken, | ||
"idToken" to credentials.idToken, | ||
"refreshToken" to credentials.refreshToken, | ||
"userProfile" to credentials.user.toMap(), | ||
"expiresAt" to formattedDate, | ||
"scopes" to scope, | ||
"tokenType" to credentials.type | ||
) | ||
) | ||
} | ||
}) | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
.../com/auth0/auth0_flutter/request_handlers/api/PhoneNumberPasswordlessApiRequestHandler.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,59 @@ | ||
package com.auth0.auth0_flutter.request_handlers.api | ||
|
||
import com.auth0.android.authentication.AuthenticationAPIClient | ||
import com.auth0.android.authentication.AuthenticationException | ||
import com.auth0.android.authentication.PasswordlessType | ||
import com.auth0.android.callback.Callback | ||
import com.auth0.auth0_flutter.request_handlers.MethodCallRequest | ||
import com.auth0.auth0_flutter.toMap | ||
import com.auth0.auth0_flutter.utils.assertHasProperties | ||
import io.flutter.plugin.common.MethodChannel | ||
import java.util.HashMap | ||
|
||
private const val PASSWORDLESS_PHONE_NUMBER_LOGIN_METHOD = "auth#passwordlessWithPhoneNumber" | ||
|
||
class PhoneNumberPasswordlessApiRequestHandler : ApiRequestHandler { | ||
override val method: String = PASSWORDLESS_PHONE_NUMBER_LOGIN_METHOD | ||
|
||
override fun handle( | ||
api: AuthenticationAPIClient, | ||
request: MethodCallRequest, | ||
result: MethodChannel.Result | ||
) { | ||
val args = request.data | ||
assertHasProperties(listOf("phoneNumber", "passwordlessType"), args) | ||
val passwordlessType = getPasswordlessType(args["passwordlessType"] as String) | ||
|
||
val builder = api.passwordlessWithSMS( | ||
args["phoneNumber"] as String, | ||
passwordlessType | ||
) | ||
|
||
if (args["parameters"] is HashMap<*, *>) { | ||
builder.addParameters(args["parameters"] as Map<String, String>) | ||
} | ||
|
||
builder.start(object : Callback<Void?, AuthenticationException> { | ||
override fun onFailure(exception: AuthenticationException) { | ||
result.error( | ||
exception.getCode(), | ||
exception.getDescription(), | ||
exception.toMap() | ||
) | ||
} | ||
|
||
override fun onSuccess(void: Void?) { | ||
result.success(void) | ||
} | ||
}) | ||
} | ||
|
||
private fun getPasswordlessType(passwordlessType: String): PasswordlessType { | ||
return when (passwordlessType) { | ||
"code" -> PasswordlessType.CODE | ||
"link" -> PasswordlessType.WEB_LINK | ||
"link_android" -> PasswordlessType.ANDROID_LINK | ||
else -> PasswordlessType.CODE | ||
} | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
auth0_flutter/darwin/Classes/AuthAPI/AuthAPIEmailPasswordlessLoginMethodHandler.swift
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,47 @@ | ||
import Auth0 | ||
|
||
#if os(iOS) | ||
import Flutter | ||
#else | ||
import FlutterMacOS | ||
#endif | ||
|
||
struct AuthAPIPasswordlessEmailMethodHandler: MethodHandler { | ||
enum Argument: String { | ||
case email | ||
case passwordlessType | ||
case parameters | ||
} | ||
|
||
let client: Authentication | ||
|
||
func handle(with arguments: [String: Any], callback: @escaping FlutterResult) { | ||
guard let email = arguments[Argument.email] as? String else { | ||
return callback(FlutterError(from: .requiredArgumentMissing(Argument.email.rawValue))) | ||
} | ||
|
||
guard let passwordlessTypeString = arguments[Argument.passwordlessType] as? String, | ||
let passwordlessType = PasswordlessType(rawValue: passwordlessTypeString) else { | ||
return callback(FlutterError(from: .requiredArgumentMissing(Argument.passwordlessType.rawValue))) | ||
} | ||
|
||
guard let parameters = arguments[Argument.parameters] as? [String: Any] else { | ||
return callback(FlutterError(from: .requiredArgumentMissing(Argument.parameters.rawValue))) | ||
} | ||
|
||
client | ||
.startPasswordless(email: email, | ||
type: passwordlessType, | ||
connection: "email" | ||
) | ||
.parameters(["authParams":parameters]) | ||
.start { | ||
switch $0 { | ||
case let .success: | ||
callback(nil) | ||
case let .failure(error): | ||
callback(FlutterError(from: error)) | ||
} | ||
} | ||
} | ||
} |
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
Oops, something went wrong.