3

Purpose of the redirect

I am working on an application that integrates with Cisco's Webex Teams Api. Unfortunately, for macOS they don't have an SDK (they only do have one for iOS), so I am trying to authenticate with their Api kind of "manually".

So, I am using a custom URL that has a client id backed in to retrieve a code. When calling this URL, Cisco's regular login procedure starts, asking the user to log in with her / his username an password. Upon successful login, Cisco's server provide you with a URL that includes a code that is then needed to proceed.

What I got so far

Custom url

My prototype currently just consists of a button that calls a custom URL to authenticate. To get this URL, I needed to register my integration with Cisco here: https://developer.webex.com/my-apps

So far, when I click on my button, a WKWebView instance takes over with my custom URL:

https://api.ciscospark.com/v1/authorize?client_id=<**personalClientId**>&response_type=code&redirect_uri=<**BundleIdentifier**>3A%2F%2Fredirect&scope=spark%3Aall%20spark%3Akms&state=set_state_here

Redirect URI

So, my redirect uri currently is ch.appfros.webexoauthwokflowprototype://redirect; this redirect uri is registered with Cisco in my integration registration form.

I am aware that I have to put this redirect uri into my app, so I did that resulting in the corresponding info.plist section looking like this:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>response</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>ch.appfros.webexoauthwokflowprototype://</string>
        </array>
    </dict>
</array>

Preparing the AppDelegate

As I far as I currently have found out, it seems that I need to also have an additional function in my AppDelegate, that handles the callback. For cocoa, this seems to be the func application(_ application: NSApplication, open urls: [URL]) method.

As far as I understand it, i would have to handle all redirects there. My AppDelegate currently looks like this:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {



    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application)
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    func application(_ application: NSApplication, open urls: [URL]) {
        print("got answer")
    }

}

The issue I am having

My problem is, fulfilling the login procedure results in the message There is no application set to open the URL ch.appfros.webexoauthwokflowprototype://redirect?code=&state=loggedIn.

So, the authentication process on Cisco's end is successful and it provides my system with the URL -- which my computer does not know how to handle... I can even see the code I need to retreive an access in the URL that is coming back, I just don't have them within my application to proceed...

What am I missing?

It's quite obvious that I am missing something crucial here -- I just don't know what it is and researching online wouldn't help me. I could find bits and pieces here and there, but I am still to find a concise instruction on what to do in my case for my macOS app.

Environment

  • Xcode 10.1
  • Swift 4.2
  • Cocoa app
  • Cisco Webex Teams integration with authentication URL

Edit: changes to AppDelegate

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {



    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application)
         NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleAppleEvent(event:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    func application(_ application: NSApplication, open urls: [URL]) {
        print("got answer")
    }

    @objc
    func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
        print("got answer in event handler")
    }

}

Edit 2: update to AppDelegate (to reflect hints and tips by NoHalfBits)

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {



    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application)

    }

    func applicationWillFinishLaunching(_ notification: Notification) {
        NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleAppleEvent(event:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    func application(_ application: NSApplication, open urls: [URL]) {
        print("got answer")
    }

    @objc
    func handleAppleEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
        print("got answer in event handler")
    }

}
appfrosch
  • 1,146
  • 13
  • 36

2 Answers2

3

In the Info.plist, specify the URL scheme in the array under the CFBundleURLSchemes key without the :// suffix (ch.appfros.webexoauthwokflowprototype instead of ch.appfros.webexoauthwokflowprototype://).

Btw, Apple recommends using a reverse-DNS style identifier for CFBundleURLName (the name, not the scheme itself).

I've set up a minimal test project: Xcode 10.1, Cocoa app Swift template. The Info.plist part defining the custom scheme is:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>no.half.bits</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>nohalfbits</string>
        </array>
    </dict>
</array>

The app delegate just is:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    func application(_ application:NSApplication, open urls: [URL]) {
        print("openURLs:", urls)
    }
}

This works as expected when testing with a small command line tool:

#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        NSURL* url = [NSURL URLWithString:@"nohalfbits://hello.world"];
        // is there app that handles the scheme, and where is it
        NSLog(@"appURL = %@", [NSWorkspace.sharedWorkspace URLForApplicationToOpenURL:url]);
        // actually open the url; the app should log this
        [NSWorkspace.sharedWorkspace openURL:url];
    }
    return 0;
}

I also added a WKWebView to the project and let it load a minimal webpage from a server, just containing a link with the custom scheme - as expected, clicking this link triggers the openURL method with the custom scheme (had to add the required NSAppTransportSecurity dictionary to the Info.plist and enable outgoing connections in the sandbox settings of the project).

In the past, I've seen some 3rd party browsers chocking on custom schemes containing hyphens and dots. For WKWebView and the underlying custom URL scheme handling in the os this seems to be no problem; no.half-bits instead of nohalfbits works as expected when testing with the NSWorkspace methods and the WKWebView.

NoHalfBits
  • 604
  • 1
  • 5
  • 10
  • Thanks for your input -- didn't help unfortunately... Tried without `://`, also tried reversing to in the end get a redirect uri with the following signature: `dq-notfall://ch.appfros.webexoauthwokflowprototype?code=&state=loggedIn` (btw I don't understand why apple does not want us to use reverse url for the URL scheme because then the scheme would be unique for pretty sure...). Had to change it on the Cisco side of things as well obviously. I am just thinking: am I running into issues because my system is not aware of the parameters coming with the url after the `?` ? – appfrosch Dec 11 '18 at 07:32
  • Sorry, seems I wasn't thorough enough in reading your question; see my updated answer – NoHalfBits Dec 11 '18 at 10:18
  • Changed my AppDelegate following your tip (see above) -- unfortunately it's still not working... Must have something wrong somewhere... – appfrosch Dec 11 '18 at 12:42
  • No, put the handler registration in `applicationWillFinishLaunching`. The AEs are sent before didFinishLaunching is reached (but should only make a difference if the custom url scheme is launching the app, not if it's already running) – NoHalfBits Dec 11 '18 at 12:50
  • Yes well, doesn't help neither -- it seems to me that macOS still isn't aware that my (already opened app) can handle this link. Updated my post above again so that you can see the current version of the AppDelegate. – appfrosch Dec 11 '18 at 16:23
1

Turns out that sandboxing really was the problem. After disabling Sandboxing -- this is going to be a small in house helper at my comanpy, nothing fancy and for sure nothing for any store -- it worked right away.

If need be I will have to take a look into how NSAppTransportSecurity would be needed to get implemented -- thanks to @NoHalfBits for pushing me into the right direction.

So now I have

  • the custom url registered
  • App Sandboxing disabled
  • func application(_ application: NSApplication, open urls: [URL]) implemented in my AppDelegate (turns out this really is enough as @Aaron Raimist was saying

Thanks for your help; hope this might help anybody else (or my future self) down the road :-)

appfrosch
  • 1,146
  • 13
  • 36