iOS Native App - OAuth with ZOOM Rest API

Description

Similar to this post we are trying to use OAuth and the Zoom Rest API in our mobile iOS app. We are having issues getting the OAuth to work in iOS. Any help is greatly appreciated!

Issue 1: Universal Link Redirects

Our plan was to use AppAuth-iOS [1] with Universal Links [2].

NOTE: AppAuth-iOS uses a ASWebAuthenticationSession to present the browser for logging in.

We are having issues with the Universal Link redirection, the Universal Link catches the redirect
when using “Sign-in with Google” but not when using “Sign-in”. See image below.

We would prefer to use Universal Links instead of using a custom URI scheme (iOS deeplinks) for two reasons:

  1. This is preferred method laid out by RFC 8252 Appendix B.1 [3]
  2. Zoom OAuth does not support custom URI schemes, only https. To use a custom URI scheme we would have to have a server redirect from https://<redirect_uri> to <custom scheme>://<redirect_uri> as suggested by this comment. This unfortunately does not play well with the AppAuth-iOS library since they have checks to ensure that the redirects match.

We can work around the above to 2 and use the server to redirect (just more work on our end). But I am curious if there is a way to get Universal Links to work with Zoom. It appears that “Sign-in with Google” uses a HTTP 302 response to trigger the redirect to the redirect_uri where as “Sign-in with Zoom” appears to use a client side redirect which does not seem to trigger the Universal Link (except the first time the user authorizes and they get the authorization prompt, in that case the Universal Link works).

Any ideas on how we could get Universal Links to work would be greatly appreciated as this is our preferred method.

Issue 2: Client Secret and Code Exchange

It appears that Zoom does not support PKCE [4] which would be recommended way for a native app to perform the OAuth exchange as it does not involve a client secret. Given this limitation it appears that the only secure way for our App to complete the OAuth exchange would be for us to create a server to perform the code exchange as described in this comment [6] (please see extra links since Zoom prevents more then 2 links in a post for new users).

Is there a simpler way of achieving the same result? It appears OAuth also has an implicit flow [5, section-1.3.2], however it sounds like that may not be secure enough or appropriate for our use case.

We have also noticed that there is 2 potential authorization urls, is there any deference between these 2?
https://zoom.us/oauth2/login
https://zoom.us/oauth/authorize

Extra Links
(due to the 2 link new user limit)

  1. https://github.com/openid/AppAuth-iOS
  2. https://developer.apple.com/documentation/xcode/allowing_apps_and_websites_to_link_to_your_content?language=objc
  3. https://tools.ietf.org/html/rfc8252#appendix-B.1
  4. https://auth0.com/docs/flows/authorization-code-flow-with-proof-key-for-code-exchange-pkce
  5. https://tools.ietf.org/html/rfc6749#section-1.3.2
  6. https://devforum.zoom.us/t/oauth-with-zoom-rest-api-from-ios/23937/4

Sample Code

Here is a stripped down example of what we doing.

NOTE: for simplicity this example code has the FE performing the code exchange.

import Foundation
import SwiftUI
import AuthenticationServices
import AppAuth

extension SceneDelegate {
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        print(userActivity)
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
               let authorizationFlow = appDelegate.currentAuthorizationFlow,
               authorizationFlow.resumeExternalUserAgentFlow(with: url) {
                appDelegate.currentAuthorizationFlow = nil
            }
        }
    }
}

extension UIApplication {
    func getTopViewController() -> UIViewController? {
        let keyWindow = self.windows.first(where: { $0.isKeyWindow })
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        }
        return keyWindow?.rootViewController
    }
}


func presentAuthSession() {
    let configuration = OIDServiceConfiguration(
        authorizationEndpoint: URL(string: "https://zoom.us/oauth2/login")!,
        tokenEndpoint: URL(string: "https://zoom.us/oauth/token")!
    )

    // builds authentication request
    let request = OIDAuthorizationRequest(
        configuration: configuration,
        clientId: "*************",
        clientSecret: "**************",
        scope: nil,
        redirectURL: URL(string: "https://oauthexample.timeless.space/zoom")!,
        responseType: "code",
        state: nil,
        nonce: nil,
        codeVerifier: nil,
        codeChallenge: nil,
        codeChallengeMethod: nil,
        additionalParameters: nil
    )

    if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
       let presentingController = UIApplication.shared.getTopViewController() {
        appDelegate.currentAuthorizationFlow = OIDAuthState.authState(
            byPresenting: request,
            presenting: presentingController
        ) { authState, error in
            guard error == nil else {
                print("Zoom Authorization Error \(error!)")
                return
            }

            if authState != nil {
                print("Success!!")
            }
        }
    } else {
        print("Cannot find top view controller")
    }
}


struct ContentView: View {
    var body: some View {
        Button("Login", action: presentAuthSession)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Hey @Timeless,

Thanks for using the dev forum and thank you for providing the thorough explanation of the OAuth issue you are facing. In your application are you using a combination of the Zoom iOS SDK and the Zoom API, or are you only using the Zoom API for authenticating the user?

Thanks!
Michael

@Michael_Condon Thank you for the prompt response, we are only using the Zoom API with OAuth for Authentication. We are not using the SDK for anything. Our app is a calendar client so our initial Zoom integration is intended to be very simple, just allowing the user to create meeting links that can be attached to a calendar event during event creation. The only endpoints we are using are /v2/users/me, /v2/users/{userId}/meetings and /v2/meetings/{meetingId}.

Hey @Timeless,

I see. This is going to take some testing on my end and may take a while. I just want to give you a headsup!

Thanks!
Michael

@Michael_Condon Thank you for looking into this! I can send you our example project as a zip if you would like (I dont know the best way to send that).

Hey @Timeless,

Yes that would indeed be very helpful :slight_smile: you can attach it to an email to DeveloperSupport@zoom.us please mention my name and a link to this post in the email so that it gets sent directly to me.

Thanks!
Michael

1 Like

@Michael_Condon Thank you for investigating this! I can send you our model undertaking as a zip in the event that you might want (I dont realize the most ideal approach to send that).

Hey @michaelhenry199,

Thanks for using the dev forum, are you with @Timeless?

Michael

@Michael_Condon, @michaelhenry199 is not with us. @michaelhenry199 are you experiencing a similar issue?

@Michael_Condon Just checking, did developerSupport@zoom.us forward my email to you?

Hey @Timeless,

Yep, I received your email and am investigating. @michaelhenry199 what is the issue you are having?

Thanks!
Michael

Hey @Timeless ,

What exactly happens the first time when it works vs. the second time when it does not work? Can you send us some more screenshots / videos?

If the redirect uri you are setting in the marketplace app settings is a URL, it should work. If it is a bug, we can investigate it and work on a fix. :slight_smile:

Thanks,
Tommy

Hi @tommy Thanks for looking into this. Im not sure if you have seen or have access to this but there is lots of additional info in this ticket (picks up after here).

Here a video (using the example app) showing:

  1. Logging in with an un-authroized account (so that the authorization screen appears)
  2. Logging in with the same account (now authorized) immediately after
    - The “Site Not Found” is displayed because the redirect is not a public site, but instead was meant to be caught as a Universal Link
  3. Shows logging in with an already authorized Google account

I guess the broader question is how Zoom recommends integrating the OAuth within a mobile iOS app, as the best practices (RFC8252 Appendix B) outline two methods:

  1. private-use URI scheme (referred to as “custom URL scheme”) - Not supported by the Zoom App dashboard (and I assume BE)
  2. claimed “https” scheme URIs (known as “Universal Links”) - Fails in case #2 above. We suspect this is the way the Zoom login page handles the redirect. We suspect it’s a client side redirect in number 2 as opposed to a server side redirect, HTTP 302 status code in cases 1 and 3. However without access to Zoom login page source code it is somewhat difficult to confirm. Technically according to RFC 6749 Section 1.7 both the redirects would be valid according to the spec.

Thanks!

Lucas Wilkinson

Hey @tommy and @Timeless,

I am curious if it is possible that Zoom Oauth is treating universal links the same way it would treat a custom scheme. However, I am puzzled as to how this could happen since Zoom Oauth can easily differentiate a custom url scheme from a standard link, but a universal link appears in a similar format to a standard link.

Thanks!
Michael

Hey @Michael_Condon,

Thank you for continuing to dig into this.

I don’t think there’s a bug in the Zoom code that only affects (identifies) the universal link so much as redirect implementation used in case 2 (from the video) is simply not compatible with “Universal Links”. This is just speculation though as there is very little we can do to introspect the authentication flow as ASWebAuthenticationSession intentionally (for security reasons I assume) prevents this. @tommy do you have insight into how the redirect is performed and if there is a difference in the implementation between case 1 and 2 (from the video)?

Thank you,

Lucas Wilkinson

Hi @Tommy did you get a chance to review the video we provided?

Hey @Timeless ,

Yes I did see the video, thank you for sending it. We are investigating this issue and will update you once we find the root cause. (ZOOM-260700)

-Tommy

Hi @Tommy just following up to see if there is any updates here