John
John

Reputation: 297

"provideCredentialWithoutUserInteractionForIdentity:" is not working

I have an app that autofills the password on iOS 12. Now I want to implement this function:

- (void)provideCredentialWithoutUserInteractionForIdentity:(ASPasswordCredentialIdentity *)credentialIdentity;

But I cant get it to work like it should.

AutoFill is enabled in settings and also enabled for my app.

I've read the documentation but this doens't help me. https://developer.apple.com/documentation/authenticationservices/ascredentialproviderviewcontroller/2977554-providecredentialwithoutuserinte?language=objc

I've tried calling this function from viewDidLoad, prepareCredentialListForServiceIdentifiers,.. but this is stupid and definitely won't work.

- (void)provideCredentialWithoutUserInteractionForIdentity:(ASPasswordCredentialIdentity *)credentialIdentity {
    ASPasswordCredential *credential = [[ASPasswordCredential alloc] initWithUser:@"theUsername" password:@"thePassword"];
    [self.extensionContext completeRequestWithSelectedCredential:credential completionHandler:nil];
}

The function should show the credentials above the keyboard, but this doesn't happen. It just shows the default "Passwords" button.

Upvotes: 2

Views: 410

Answers (2)

Theodor Alx
Theodor Alx

Reputation: 1

Let me add to this topic in case this is still relevant:

First of all, make sure you have the capabilities enabled to provide passwords:

Go to your autofill extension target:

Info.plist
  NSExtension
    NSExtensionAttributes
      ASCredentialProviderExtensionCapabilities
        ProvidesPasswords: Boolean -> YES

Now, in your app, as mentioned above, you should make sure that you have stored a list of ASPasswordCredentialIdentities into your ASCredentialIdentityStore.

Let me provide you a some sample code:

func saveCredential(username: String, password: String) {
    let serviceIdentifier = ASCredentialServiceIdentifier(identifier: "netflix.com", type: .domain)
    let credential = ASPasswordCredentialIdentity(serviceIdentifier: serviceIdentifier, user: username, recordIdentifier: "record_id_in_your_database")

        ASCredentialIdentityStore.shared.saveCredentialIdentities([credential]) { success, error in ... }

}

This will prompt the keyboard to display the text that lets you select that credential, and then calls provideCredentialWithoutUserInteractionForIdentity, where you will be able find the password for that domain in your database using "record_id_in_your_database"

Alternatively, you can create multiple multiple ASPasswordCredentialIdentities and store them and keep them up to date using the method ASCredentialIdentityStore.shared.replaceCredentialIdentities(yourListOfIdentities)

Upvotes: 0

Buffer Underflow
Buffer Underflow

Reputation: 646

  1. Make sure you have some ASPasswordCredentialIdentitys for your domain/url in ASCredentialIdentityStore. (These are records with some unique recordIdentifier, that doesn't hold password but hold data that can help you decide what password you should take from some secure storage of your choice.)
  2. When you open a website, iOS looks up the ASCredentialIdentityStore, and shows a big button for quick login if there's a matching record. If you hit the button - this is only when provideCredentialWithoutUserInteraction callback is executed. Your task is to work with ASPasswordCredentialIdentity passed as an argument (it has recordIdentifier field) and find matching password for it (in your database/keychain/etc.) When you have password - you create ASPasswordCredential and pass it to self.extensionContext.completeRequest. Also make sure to call extensionContext.cancelRequest in case of any errors.

here's my example

override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
           
        let databaseIsUnlocked = false
        if (databaseIsUnlocked) {
            
            // this function queries my custom keychain records by recordIdentifier (Generic Passwords) to find matching password
            getItemForRecordId(identifier: credentialIdentity.recordIdentifier) { password, err in
                
                guard password != nil else {
                    print("password was nil")
                    self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
                    return;
                }
                let passwordCredential = ASPasswordCredential(user: credentialIdentity.user, password: password as! String);
                self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil);
                
            };
            
        } else {
            self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
        }
    }

Upvotes: 0

Related Questions