Help with Passkeys & KeyChain

Hello all,

I have a standard sign up form on my iOS app with the typical username and password fields. I'm using SwiftUI and have marked my TextField with the correct .textContentType.

TextField("Username", text: $username)
    .padding()
    .background(Color(.systemGray6))
    .cornerRadius(8)
    .padding(.bottom, 10)
    .textContentType(.username)

I created this function inside the same swift file that I use to handle my registrations and signins.

func fetchChallenge(completion: @escaping (Data?) -> Void) {
    let url = URL(string: "https://www.myurl.com/api/generate_challenge.php")!
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let data = data, error == nil {
            completion(data)
        } else {
            completion(nil)
        }
    }.resume()
}

This is what this server file looks like.

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

function generate_challenge() {
    $randomBytes = openssl_random_pseudo_bytes(32);
    return base64_encode($randomBytes);
}

$challenge = generate_challenge();
$response = array("challenge" => $challenge);

echo json_encode($response);
?>

My first question would be, is this what the new PassKey is expecting as a challenge? It says it should be unique each time, so I'm assuming that it doesn't need to be saved to the database. Is that correct? Or would this be considered to be the public key?

Next, how can I use the examply Shiny PassKey code in SwiftUI to call my functions and use passkeys and keychain? e.g. My custom functions....

func signInWithV1(username: String, password: String)
func signUpWithV1(userName: String, password: String)

My server is a linux server running nginx. I just can't seem to find anything on how to properly do this on the server side. I'm not sure what to save to the database and how you would integrate the above functions into the authorizationController and how to properly verifiy the let variables in the example code that I found from the Shiny Project.

If anyone has time to explain this I would be extrememly grateful! I'm assuming that I can't just use the Shiny code as is since it says that I need to Verify stuff inside the authorizationController. As of right now, my app simply saves the username and password to my database and I do checks to ensure the username and password is how I want it on the server side and the same when they login. However, nothing is saved to the keychain or passkey as of right now.

Thanks in advance to anyone who takes the time to explain this in detail! I would be very grateful!!

Replies

There are a bunch of questions here, and I'm going to do my best to answer all of them:

  1. The challenge should be securely random data that is unique each time. One of the goals of having the unique challenge is to prevent replay attacks, which means someone recording the message sent during a sign in and reusing it later to spoof a second sign in. If your server uses a unique challenge for every sign in, then replaying won't work because the challenge isn't correct. This also means your server needs to keep track of challenges that it recently generated, so that it can make sure each sign in is using a challenge that actually came from the server. However, a simple server likely doesn't need to persist the challenge after it is used.
  2. The challenge is different from the public key. The public key is contained in the attestation object, and does need to be persisted indefinitely.
  3. ASAuthorizationController predates SwiftUI and can be difficult to use in pure SwiftUI apps, due to its reliance on presentation anchors. New in iOS 16.4 we've introduced a SwiftUI-friendly AuthorizationController alternative to solve this problem.
  4. If you use an ASAuthorizationPasswordRequest in addition to an ASAuthorizationPlatformPublicKeyCredentialAssertionRequest, the system UI will allow signing in with either a password or passkey, leaving the decision up to the user. You can then inspect the ASAuthorizationResult to determine which type of credential was used and complete the sign in appropriately.
  5. For the server side, passkeys are standard WebAuthn credentials. Any WebAuthn-compatible server implementation should work for passkeys, so there are a wide range of options and it's difficult to give specific guidance. Most backend platforms have a range of WebAuthn libraries available, so you should be able to find one that meets your needs.
  6. It's "passkey", with a lowercase "p" and "k", unless it starts a sentence 🙂.