Skip to content

Latest commit

 

History

History
151 lines (115 loc) · 6 KB

OpenIDConnect.md

File metadata and controls

151 lines (115 loc) · 6 KB

OpenIDConnect

This package contains helper classes and methods to simplify the implementation of AppAuth when you need to connect with openID authentication.

The OpenIDConnectManager has just one method, requestAccessToken that will try to connect to the .well-known/openid-configuration of your openID server. It needs a OpenIDConnectConfiguration with the right server url, a clientId and a redirectUri for redirecting from the openID server back to the app.

The next parameter to pass to the requestAccessToken call is an optional UIViewController. If you provide a viewContoller, it will use that to present a modal with the login page from the openID server (and stay in the app). If you don't provide a viewController, it will launch a default browser from the OS with the login page.

If the login is successful, you will get an OpenIDConnectToken as a result. It contains the Access Token (String) generated by the authorization server and an ID Token value associated with the authenticated session (JSON). See the openID specs for more details.

A list of all the different errors that the openID server can respond can be found here.

Usage

For each openID server you want to connect with, you'll need to create an OpenIDConnectConfiguration

import OpenIDConnect

class MyOpenIDConnectConfiguration: OpenIDConnectConfiguration {
	
	var issuerUrl: URL {return URL(string: "url of the openID server")}
	var clientId: String {return "the Id for the client (this app)"}
	var redirectUri: URL {return URL(string: "url to call when the call is completed which redirect to the app")}
}

An example of a ViewModel presenting a browser for the user to connect to the openID server:

class AuthenticationViewModel {
	private let openIDConnectManager: OpenIDConnectManaging?
	private let openIDConnectState: OpenIDConnectState?
	private let openIDConnectConfiguration: OpenIDConnectConfiguration
	
	init(
		openIDConnectManager: OpenIDConnectManaging?,
		openIDConnectConfiguration: OpenIDConnectConfiguration = MyOpenIDConnectConfiguration(),
    openIDConnectState: OpenIDConnectState? = UIApplication.shared.delegate as? OpenIDConnectState){
			self.openIDConnectManager = openIDConnectManager
      self.openIDConnectState = openIDConnectState
			self.openIDConnectConfiguration = openIDConnectConfiguration
		}
	
	// use the internal browser for web based openID,
	// use the external browser for app based openID, then set presentingViewController to nil
	func login(presentingViewController: UIViewController?) {
		
		openIDConnectManager?.requestAccessToken(
			issuerConfiguration: openIDConnectConfiguration,
			presentingViewController: presentingViewController,
			onCompletion: { (token: OpenIDConnectToken) in
				// Success! We now have the token from the openID server.
				// It is an openIDConnectToken, with the fields: idToken and accessToken.
			},
			onError: { error in
				if let error {
					if error.localizedDescription.contains("saml_authn_failed") ||
						error.localizedDescription.contains("cancelled:") ||
						error.code == OIDErrorCode.userCanceledAuthorizationFlow.rawValue {
						print("User cancelled")
						// handle the case the user cancelled the flow
						return
					} else {
						print(error)
					}
				}
			}
		)
	}
}

The authorization response URL is returned to the app via the iOS openURL app delegate method, so you need to pipe this through to the current authorization session (created in the previous session):

import OpenIDConnect

@main
class AppDelegate: UIResponder, UIApplicationDelegate, OpenIDConnectState {

	// login flow
	var currentAuthorizationFlow: OIDExternalUserAgentSession?
	
  ...
  
	/// For handling __Deep Links__ only, - not relevant for Universal Links.
	func application(
		_ app: UIApplication,
		open url: URL,
		options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
			
			// Sends the URL to the current authorization flow (if any) which will
			// process it if it relates to an authorization response.
			if let authorizationFlow = self.currentAuthorizationFlow,
			   authorizationFlow.resumeExternalUserAgentFlow(with: url) {
				self.currentAuthorizationFlow = nil
				return true
			}
			
			// Your additional URL handling (if any)
			
			return false
		}
  
	/// Entry point for Universal links in iOS 11/12 only (see SceneDelegate for iOS 13+)
	/// Used for both running and cold-booted apps
	func application(_: UIApplication, continue userActivity: NSUserActivity, restorationHandler _: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
		
		// Apple's docs specify to only handle universal links "with the activityType set to NSUserActivityTypeBrowsingWeb"
		guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL
		else { return false }
		
		if url.path.hasPrefix("path from MyOpenIDConnectConfiguration redirect uri"),
			let authorizationFlow = self.currentAuthorizationFlow,
			authorizationFlow.resumeExternalUserAgentFlow(with: url) {
				self.currentAuthorizationFlow = nil
				return true
		}
		return false
	}
	
}

If you are using a SceneDelegate:

import OpenIDConnect

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
...
  	func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {

		if let url = URLContexts.first?.url,
		   let openIDConnectState = UIApplication.shared.delegate as? OpenIDConnectState,
		   let authorizationFlow = appAuthState.currentAuthorizationFlow,
		   authorizationFlow.resumeExternalUserAgentFlow(with: url) {
			openIDConnectState.currentAuthorizationFlow = nil
		}
	}
}
  

License

License is released under the EUPL 1.2 license. See LICENSE for details.