auth0 / auth0-flutter

Auth0 SDK for Flutter
https://pub.dev/documentation/auth0_flutter/latest/
Apache License 2.0
59 stars 39 forks source link

Bug in CredentialsManagerSaveMethodHandler #304

Closed erdzan12 closed 10 months ago

erdzan12 commented 1 year ago

Checklist

Description

It looks like the CredentialsManagerSaveMethodHandler has a bug when trying to decode the Credentials class from the input in here https://github.com/auth0/auth0-flutter/blob/main/auth0_flutter/ios/Classes/CredentialsManager/CredentialsManagerSaveMethodHandler.swift#L13C23-L13C23 It always throws this error:

CredentialsManagerException (SWIFT_REQUIRED_ARGUMENT_MISSING: The required argument 'credentials' is missing or has the wrong type.)

When updating the class to:

import Flutter
import Auth0

struct CredentialsManagerSaveMethodHandler: MethodHandler {
    enum Argument: String {
        case credentials
    }

    let credentialsManager: CredentialsManager

    func handle(with arguments: [String: Any], callback: @escaping FlutterResult) {
        do {
            let credentialsDictionary = arguments[Argument.credentials] as? [String: Any]
            let jsonData = try JSONSerialization.data(withJSONObject: credentialsDictionary, options: [])
            let credentials = try JSONDecoder().decode(Credentials.self, from: jsonData)
            callback(self.credentialsManager.store(credentials: credentials))
        } catch {
            print("Error decoding credentials: \(error)")
            callback(FlutterError(from: .requiredArgumentMissing("Failed to decode credentials: \(error.localizedDescription)")))
        }
    }
}

It seems to work.

Here is how in the Pod (Auth0) the Credentials are decoded:

extension Credentials: Codable {

    enum CodingKeys: String, CodingKey {
        case accessToken = "access_token"
        case tokenType = "token_type"
        case expiresIn = "expires_in"
        case refreshToken = "refresh_token"
        case idToken = "id_token"
        case scope
        case recoveryCode = "recovery_code"
    }

    /// `Decodable` initializer.
    public convenience init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let accessToken = try values.decodeIfPresent(String.self, forKey: .accessToken)
        let tokenType = try values.decodeIfPresent(String.self, forKey: .tokenType)
        let idToken = try values.decodeIfPresent(String.self, forKey: .idToken)
        let refreshToken = try values.decodeIfPresent(String.self, forKey: .refreshToken)
        let scope = try values.decodeIfPresent(String.self, forKey: .scope)
        let recoveryCode = try values.decodeIfPresent(String.self, forKey: .recoveryCode)

        var expiresIn: Date?
        if let string = try? values.decode(String.self, forKey: .expiresIn), let double = Double(string) {
            expiresIn = Date(timeIntervalSinceNow: double)
        } else if let double = try? values.decode(Double.self, forKey: .expiresIn) {
            expiresIn = Date(timeIntervalSinceNow: double)
        } else if let date = try? values.decode(Date.self, forKey: .expiresIn) {
            expiresIn = date
        }

        self.init(accessToken: accessToken ?? "",
                  tokenType: tokenType ?? "",
                  idToken: idToken ?? "",
                  refreshToken: refreshToken,
                  expiresIn: expiresIn ?? Date(),
                  scope: scope,
                  recoveryCode: recoveryCode)
    }

}

Additionally, we pass in expiredAt, even though expiredIn is required, is this intended?

Here is also another user stating the same issue: https://community.auth0.com/t/flutter-native-apple-sign-in-using-custom-ui/114027

Reproduction

Try to save some Credentials with the default credentialsManager like this:

        var creds = Credentials(
            accessToken: accessToken,
            tokenType: 'Bearer',
            idToken: idToken,
            refreshToken: null,
            expiresAt: DateTime.now().add(Duration(
                seconds:
                    100)),
            scopes: {},
            user: UserProfile(sub: sub!));
        await sl<Auth0>().credentialsManager.storeCredentials(creds);

Additional context

No response

auth0_flutter version

1.2.1

Flutter version

3.13.2

Platform

iOS

Platform version(s)

No response

Widcket commented 11 months ago

Hi @erdzan12, thanks for raising this, and apologies for the delay.

I've been a bit busy and was unable to take a look at this yet, will try to get back before the end of the week.

Widcket commented 11 months ago

Hi @erdzan12,

The Swift layer expects the date passed to expiredAt to be UTC. This is already the case for dates that come from the Swift layer (e.g. automatic save to Keychain after Web Auth), but in this case, you're supplying your own Dart date object.

This should be handled by the SDK's Dart layer anyway (I'll look into this with more detail and test a possible solution), but as a workaround make sure to call toUtc() on your date object before passing it to Credentials.

erdzan12 commented 10 months ago

thanks

Widcket commented 10 months ago

The fix is now out in v1.3.1.