Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added support for passwordless authentication #503

Merged
merged 23 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
41f573a
Added passwordless login to android
pmathew92 Jan 28, 2025
a333817
Added passworless login for ios platform
pmathew92 Jan 28, 2025
daf0e1a
Added API doc comments
pmathew92 Jan 28, 2025
61a506f
Code refactoring and fixed build error
pmathew92 Jan 29, 2025
79a8abe
Merge branch 'main' into SDK-5524
Widcket Jan 29, 2025
8e84622
Merge branch 'main' into SDK-5524
Widcket Jan 29, 2025
0771127
Merge branch 'main' of https://github.com/auth0/auth0-flutter into SD…
pmathew92 Jan 30, 2025
240ac76
Addressed review comments
pmathew92 Jan 30, 2025
d14a5b8
Addressed comments with respect to api name changes
pmathew92 Jan 30, 2025
9c57b26
Merge branch 'main' into SDK-5524
Widcket Jan 30, 2025
2dfd95e
Added parameters option in the api
pmathew92 Jan 30, 2025
965c581
Nesting the parameters in the request for ios
pmathew92 Jan 30, 2025
da6b53c
Fixed the methid channel null error
pmathew92 Jan 30, 2025
4a26de4
Generated system link files
pmathew92 Jan 30, 2025
04ad909
Added scopes as list with default value
pmathew92 Jan 30, 2025
3a9bb14
Refactored code for analize pipeline job
pmathew92 Jan 30, 2025
235c933
Updated the examples documentation
pmathew92 Jan 30, 2025
f366b4b
Merge branch 'main' into SDK-5524
pmathew92 Jan 30, 2025
b213e5e
Minor change in doc
pmathew92 Jan 30, 2025
7c1584a
Minor formatting issues
pmathew92 Jan 31, 2025
74e91cd
Fixed flutter ananlyze error
pmathew92 Jan 31, 2025
9073a89
Added value type to passwordless type enum
pmathew92 Jan 31, 2025
31a8049
Merge branch 'main' of https://github.com/auth0/auth0-flutter into SD…
pmathew92 Jan 31, 2025
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
# Global coverage
coverage/

appium-test/node_modules/*
appium-test/node_modules/*

.idea/*
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class Auth0FlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
LoginApiRequestHandler(),
LoginWithOtpApiRequestHandler(),
MultifactorChallengeApiRequestHandler(),
PasswordlessWithEmailRequestHandler(),
PasswordlessWithPhoneNumberRequestHandler(),
LoginWithEmailRequestHandler(),
LoginWithPhoneNumberRequestHandler(),
SignupApiRequestHandler(),
UserInfoApiRequestHandler(),
RenewApiRequestHandler(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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

private const val EMAIL_LOGIN_METHOD = "auth#loginWithEmail"

class LoginWithEmailRequestHandler : 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,
args["connection"] as? String ?: "email"
).apply {
if (args.containsKey("scope")) {
setScope(args["scope"] as String)
}
if (args.containsKey("audience")) {
setAudience(args["audience"] as 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
)
)
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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

private const val SMS_LOGIN_METHOD = "auth#loginWithPhoneNumber"

class LoginWithPhoneNumberRequestHandler : 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,
args["connection"] as? String ?: "sms"
).apply {
if (args.containsKey("scope")) {
setScope(args["scope"] as String)
}
if (args.containsKey("audience")) {
setAudience(args["audience"] as 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
)
)
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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

private const val PASSWORDLESS_EMAIL_LOGIN_METHOD = "auth#passwordlessWithEmail"

class PasswordlessWithEmailRequestHandler : 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,
args["connection"] as? String ?: "email"
)

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(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
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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

private const val PASSWORDLESS_PHONE_NUMBER_LOGIN_METHOD = "auth#passwordlessWithPhoneNumber"

class PasswordlessWithPhoneNumberRequestHandler : 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,
args["connection"] as? String ?: "sms"
)

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
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Auth0

#if os(iOS)
import Flutter
#else
import FlutterMacOS
#endif

struct AuthAPIEmailPasswordlessLoginMethodHandler: MethodHandler {
enum Argument: String {
case email
case passwordlessType
case connection
pmathew92 marked this conversation as resolved.
Show resolved Hide resolved
}

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)))
}

let connection = arguments[Argument.connection] as? String ?? "email"

client
.startPasswordless(email: email,
type: passwordlessType,
connection: connection
)
.start {
switch $0 {
case let .success:
callback(nil)
case let .failure(error):
callback(FlutterError(from: error))
}
}
}
}
8 changes: 8 additions & 0 deletions auth0_flutter/darwin/Classes/AuthAPI/AuthAPIHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public class AuthAPIHandler: NSObject, FlutterPlugin {
case userInfo = "auth#userInfo"
case renew = "auth#renew"
case resetPassword = "auth#resetPassword"
case passwordlessWithEmail = "auth#passwordlessWithEmail"
case passwordlessWithPhoneNumber = "auth#passwordlessWithPhoneNumber"
case loginWithEmail = "auth#loginWithEmail"
case loginWithPhoneNumber = "auth#loginWithPhoneNumber"
}

private static let channelName = "auth0.com/auth0_flutter/auth"
Expand Down Expand Up @@ -55,6 +59,10 @@ public class AuthAPIHandler: NSObject, FlutterPlugin {
case .userInfo: return AuthAPIUserInfoMethodHandler(client: client)
case .renew: return AuthAPIRenewMethodHandler(client: client)
case .resetPassword: return AuthAPIResetPasswordMethodHandler(client: client)
case .passwordlessWithEmail: return AuthAPIEmailPasswordlessLoginMethodHandler(client: client)
case .passwordlessWithPhoneNumber: return AuthAPIPhoneNumberPasswordlessLoginMethod(client: client)
pmathew92 marked this conversation as resolved.
Show resolved Hide resolved
case .loginWithEmail: return AuthAPILoginWithEmailMethodHandler(client: client)
case .loginWithPhoneNumber: return AuthAPILoginWithPhoneNumberMethodHandler(client: client)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Auth0

#if os(iOS)
import Flutter
#else
import FlutterMacOS
#endif

struct AuthAPILoginWithEmailMethodHandler: MethodHandler {

enum Argument: String {
case email
case verificationCode
case scope
case audience
}

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 verificationCode = arguments[Argument.verificationCode] as? String else {
return callback(FlutterError(from: .requiredArgumentMissing(Argument.verificationCode.rawValue)))
}

guard let scope = arguments[Argument.scope] as? String else {
return callback(FlutterError(from: .requiredArgumentMissing(Argument.scope.rawValue)))
}

let audience = arguments[Argument.audience] as? String

client
.login(email: email,
code: verificationCode,
audience: audience,
scope: scope
)
.start {
switch $0 {
case let .success(credentials): callback(result(from: credentials))
case let .failure(error):callback(FlutterError(from: error))
}
}
}
}
Loading
Loading