Supporting 3D Secure Authentication on iOS

    Learn about how to use the iOS SDK to perform additional required authentication.

    The Stripe iOS SDK allows your app to perform 3D Secure authentication natively, without redirecting to a browser. If authentication is required, it displays a modal on top of your app to authenticate your customer.

    Supporting additional authentication in your app requires you to:

    1. Implement the STPAuthenticationContext protocol
    2. Set up a return URL (optional)
    3. Use one the methods provided by STPPaymentHandler

    3D Secure is only supported for the Payment Intents API and the Setup Intents API. If you are using the Charges API, you'll need to migrate first.

    Step 1: Implement the STPAuthenticationContext protocol

    If you're using Standard UI Components, skip this step and pass the STPPaymentContext instance as the authentication context in step 3.

    The STPAuthenticationContext protocol has two methods:

    • authenticationPresentingViewController - Returns a UIViewController instance that the iOS SDK can use to present view controllers required for authentication. For example, you might use the UIViewController the customer is on when they are ready to complete their purchase.
    • prepareAuthenticationContextForPresentation: - If implemented, this method is called before the iOS SDK presents view controllers required for authentication. You must implement this if you accept Apple Pay; for security, it's not possible to present UI over the Apple Pay sheet. Dismiss the PKPaymentAuthorizationViewController and call completion in the dismiss completion block. Once dismissed, paymentAuthorizationViewControllerDidFinish:isn't called; make sure you still complete your checkout flow in this case.

    extension MyCheckoutViewController: STPAuthenticationContext {
        func authenticationPresentingViewController() -> UIViewController {
            return self
        }
    
        // Implement this if you accept Apple Pay
        func prepareAuthenticationContextForPresentation(completion: @escaping STPVoidBlock) {
            if isPresentingApplePay {
                dismiss(animated: true) {
                    // Note paymentAuthorizationViewControllerDidFinish: isn't called
                    // after it's dismissed
                    completion()
                }
            } else {
                completion()
            }
        }
    }
    @interface MyCheckoutViewController() <STPAuthenticationContext>
    // ...
    @end
    
    @implementation MyCheckoutViewController
    // ...
    #pragma mark - STPAuthenticationContext
    
    - (UIViewController *)authenticationPresentingViewController {
        return self;
    }
    
    // Implement this if you accept Apple Pay
    - (void)prepareAuthenticationContextForPresentation:(STPVoidBlock)completion {
        if (self.isPresentingApplePay) {
            [self dismissViewControllerAnimated:YES completion:^{
                // Note paymentAuthorizationViewControllerDidFinish: isn't called
                // after it's dismissed
                completion();
            }];
        } else {
            completion();
        }
    }
    @end

    Step 2: Set up a return URL (optional)

    The iOS SDK can present a webview in your app to authenticate your customer instead of a native view (for example, if your customer is using an outdated app version). When authentication is finished, the webview can automatically dismiss itself instead of having your customer close it. To enable this behavior, configure a custom URL scheme or universal linkand set up your app delegate to forward the URL to the SDK.

    // This method handles opening custom URL schemes (e.g., "your-app://")
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        let stripeHandled = Stripe.handleURLCallback(with: url)
        if (stripeHandled) {
            return true
        } else {
            // This was not a Stripe url – handle the URL normally as you would
        }
        return false
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
            if let url = userActivity.webpageURL {
                let stripeHandled = Stripe.handleURLCallback(with: url)
                if (stripeHandled) {
                    return true
                } else {
                    // This was not a Stripe url – handle the URL normally as you would
                }
            }
        }
        return false
    }
    // This method handles opening custom URL schemes (e.g., "your-app://")
    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
        BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url];
        if (stripeHandled) {
            return YES;
        } else {
            // This was not a Stripe url – handle the URL normally as you would
        }
        return NO;
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
        if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
            if (userActivity.webpageURL) {
                BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:userActivity.webpageURL];
                if (stripeHandled) {
                    return YES;
                } else {
                    // This was not a Stripe url – handle the URL normally as you would
                }
                return NO;
            }
        }
        return NO;
    }

    Next, pass the URL as the return_url when you confirm the PaymentIntent (if you're collecting a payment) or SetupIntent (if you're saving card details). See the guides in the next step to learn more about Intents. While redirecting the user back to your app is the best user experience, you can also pass the URL of your own web page to display after webview-based authentication is finished.

    Step 3: Call STPPaymentHandler

    STPPaymentHandler provides a single method to call that handles any authentication actions for you and returns the outcome through a completion handler. Which method to call depends on your use case:

    The 3D Secure flow

    During 3D Secure authentication, the iOS SDK will present a loading screen over the UIViewController provided by your STPAuthenticationContext instance. Depending on whether additional customer interaction is required, the SDK will either dismiss the loading screen or present additional screens to collect more information. When the 3D Secure flow has completed, timed out, or been canceled by the customer, the SDK will dismiss any presented views and call the completion block passed to STPPaymentHandler.

    Checkout Screen

    Step 1: The customer enters their card details.

    Confirm PaymentIntent

    Step 2: Confirm the PaymentIntent.

    Loading screen

    Step 3: The customer's bank checks if authentication is required.

    Challenge flow screen

    Step 4: If required by their bank, the customer completes an additional authentication step.

    Customization

    STPPaymentHandler also provides options to customize the 3D Secure experience for your users via the threeDSCustomizationSettings property. The STPThreeDSCustomizationSettings class contains the customizable items for 3D Secure UI.

    Customizing the UI

    The uiCustomization property allows you to provide an STPThreeDSUICustomization instance to control the look of views presented by the SDK during 3D Secure authentication. For each customizable element of the UI, like the navigation bar or the Submit button, there is a corresponding class with properties to configure colors, fonts, text, borders, etc.

    See the STPThreeDSUICustomization documentation for a detailed explanation of each parameter.

    The following example code demonstrates some of the configuration for a "Dark Mode" themed UI.

    let uiCustomization = STPPaymentHandler.shared().threeDSCustomizationSettings.uiCustomization
    uiCustomization.textFieldCustomization.keyboardAppearance = .dark
    uiCustomization.navigationBarCustomization.barStyle = .black
    uiCustomization.navigationBarCustomization.textColor = .white
    uiCustomization.buttonCustomization(for: .cancel).textColor = .white
    STPThreeDSUICustomization *uiCustomization = [STPPaymentHandler sharedHandler].threeDSCustomizationSettings.uiCustomization;
    uiCustomization.textFieldCustomization.keyboardAppearance = UIKeyboardAppearanceDark;
    uiCustomization.navigationBarCustomization.barStyle = UIBarStyleBlack;
    uiCustomization.navigationBarCustomization.textColor = UIColor.whiteColor;
    [uiCustomization buttonCustomizationForButtonType:STPThreeDSCustomizationButtonTypeCancel].textColor = UIColor.whiteColor;

    There are 4 different types of challenge screens that may be presented to your customer. Test your UI customization with these different screens with your customization.

    Timeout

    The authenticationTimeout property controls how long the 3D Secure authentication process will run before it times out. This duration includes both network round trips and awaiting customer input. Note that this value must be at least 5 minutes in order to remain compliant with Strong Customer Authentication. A value less than 5 minutes will result in an error. If authentication times out, STPPaymentHandler will report an error with code STPPaymentHandlerTimedOutErrorCode in the completion block.

    Testing

    It's important to thoroughly test your 3D secure integration and any customization you've applied to make sure you're correctly handling cards that require additional authentication. Use these card numbers in test mode with any expiration date in the future and any three digit CVC code to trigger the different challenge screens that may be shown and validate your integration.

    Number Challenge Flow Description
    4000000000003220 Out of Band 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with Out of Band UI.
    4000000000003238 One Time Passcode 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with One Time Passcode UI.
    4000000000003246 Single Select 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with single-select UI.
    4000000000003253 Multi Select 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with multi-select UI.

    Use these cards in your application or the Standard or Custom Integration Example Apps to test the different 3D Secure challenge flows.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    Questions?

    We're always happy to help with code or other questions you might have. Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    On this page