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.
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 is released under the EUPL 1.2 license. See LICENSE for details.