OAuth2Authenticator

public class OAuth2Authenticator : OAuth2Authentication

OAuth2Authenticator


The OAuth2Authenticator provides OAuth authentication and authorization functionality to your project as defined in the OAuth 2.0 specification, and allows authentication of users without intercepting any secure workflows.

It is rare for a developer to access the OAuth2Authenticator directly — generally, the OAuth2Observer should be used.

The OAuth 2.0 specification defines the following four grant types:

  1. Authorization Code Grant
  2. Implicit Grant
  3. Resource Owner Password Credentials grant
  4. Client Credentials Grant

This component currently supports:

The standard OAuth 2.0 flow implementation is described here: OAuth 2.0 at SAP.

The OAuth2Authenticator authenticates the user on a secure web view and acquires an OAuth2 token which contains session-specific information.

For the authentication process the authenticator uses one of the web view presenting instances to present a web view in a seperate view controller. The default web view presenter on initialization is an instance of the SFSafariViewControllerPresenter which you can change if needed.

You can also use the authenticator to refresh an invalid token, for example if the OAuth2 token expired.

Usage

Setting up an OAuth2Authenticator

Authorization Code Grant

In order to implement the OAuth 2.0 authentication in your application you will need to create or acquire several components.

  • For the OAuth 2.0 authentication flow you will need few URLs and additional properties which are wrapped in the OAuth2AuthenticationParameters.
    • authorizationEndpointURL: This URL loads the login page and gets the additional parameters as GET parameters.
    • redirectURL: This is the URL which will be loaded at the end of the web view login process. The OAuth 2.0 code grant will be appended to the end of this URL as a GET parameter.
    • tokenEndpointURL: This URL has the functionality to exchange OAuth 2.0 code grant for a OAuth 2.0 token.
    • clientID: The identifier of the client that is configured on the OAuth2 server.
    • clientSecret: The client secret. Optional.
    • requestingScopes: Set of scopes that the client should request.
    • state: An opaque value used by the client to maintain state between the request and callback. Optional.
  • OAuth 2.0 authentication requires a web view login process for which a web view presenter needed. There are several default implementations for this component and the OAuth2Authenticator uses the SFSafariViewControllerPresenter by default.
  • At the end of the successful web view login the client acquires a so called OAuth 2.0 code grant. This token can be exchanged for a OAuth 2.0 token. The exchange process is done in a way of a HTTP request for which an instance of the SAPURLSession is required.

Once an OAuth 2.0 token is acquired it is neccessary to attach its access token to all HTTP request directed to OAuth 2.0 protected resources. An OAuth 2.0 token has an expiration time. Once the token is expired it can no longer be used to access OAuth 2.0 protected resources. In this case the token needs to be refreshed using the OAuth2Authenticator which acquires a new token. When you are using OAuth2Observer both of this matter are handled automatically.

Here is the step-by-step description of the assembly process:

  1. Create or obtain the SAPURLSession instance which will be used to exchange the OAuth 2.0 code grant for the OAuth 2.0 token and to refresh old (expired) OAuth 2.0 tokens.
  2. Create the wrapping OAuth2AuthenticationParameters instance with the above mentioned properties.
  3. Create the OAuth2Authenticator instance with the previously created SAPURLSession and OAuth2AuthenticationParameters instances.
  4. The authenticator uses a SFSafariViewControllerPresenter by default to present a web view for the authentication process. This requires additional steps for it to work. See Using the default web view presenter Using the default web view presenter.
  5. (recommended) If you want to use the authenticator automatically for requests, create an OAuth2Observer instance with the authenticator and register it to the SAPURLSession using the func register(_:) method. You will need an instance of OAuth2TokenStore to create an OAuth2Observer.
  6. (optional) The default web view presenter is SFSafariViewControllerPresenter. There are two more default presenters which can be used: UIWebViewPresenter and WKWebViewPresenter. Create an instance of the desired presenter and pass it to the authenticator’s initializer.

This sample code demonstrates setting up the authenticator:

// Create the wrapper with the server side configuration
let oauth2AuthenticationParameters = OAuth2AuthenticationParameters(authorizationEndpointURL: <#AuthorizationEndpoint URL#>, clientID: <#ClientID hash#>, redirectURL: <#Redirect URL#>, tokenEndpointURL: <#TokenEndpoint URL#>, requestingScopes: <#Requesting scopes#>)

// Create (or acquire) the SAPURLSession. This instance will be used for exchanging code grant for token, and for refreshing an expired token.
let urlSession = SAPURLSession()

// Create the authenticator
let authenticator = OAuth2Authenticator(authenticationParameters: oauth2AuthenticationParameters, urlSession: urlSession)

// recommended step (5.)
let observer = OAuth2Observer(authenticator: authenticator, tokenStore: self)
urlSession.register(observer)

// optional step (6.)
let authenticatorWithCustomWebViewPresenter = OAuth2Authenticator(authenticationParameters: oauth2AuthenticationParameters, urlSession: urlSession, webViewPresenter: WKWebViewPresenter())

Client Credentials Grant

// Create the wrapper with the server side configuration
let tokenEndpointURL = URL(string: "<#URL String#>")!
let scopes = Set<String>()

let name = "<#Name#>"
let secret = "<#Secret#>"

let parameters = OAuth2ClientCredentialsAuthenticationParameters(tokenEndpointURL: tokenEndpointURL, requestingScopes: scopes, name: name, secret: secret)

// Create the OAuth2Observer using the convenience initializer
let authenticator = OAuth2Authenticator(clientCredentialsAuthenticationParameters: parameters)

In the example above it is assumed that the creator class implements the OAuth2TokenStore protocol so you can use self as the tokenStore property in the OAuth2Observer‘s initializer.

Requesting OAuth2 tokens (authenticate)

Calling the OAuth2Authenticator’s func authenticate(completionHandler:) method authenticates the user at the OAuth 2 server and retrieves an OAuth 2 token using the SAPURLSession provided at initialization. The obtained token (or the error during the process) is received in the completion handler:

authenticator.authenticate { token, error in
    if let error = error {
        // If there was an error during the authentication flow then the error can be handled here.
        // Request towards OAuth 2 protected resources will fail without a successful OAuth2 authentication and token acquisition.
        return
    }
    // When the error property is nil that means the OAuth 2 authentication was successful and a OAuth 2 token is acquired.
    // Store the acquired OAuth 2 token and add its access token to all HTTP request directed to OAuth 2 protected resources.
}

Refreshing OAuth2 tokens

The refresh process acquires a new OAuth 2 token based on the old one, meaning no web view authentication required from the user side. Refreshing can become necessary for several reasons, for example if your token expires.

Calling the OAuth2Authenticator’s func refresh(token:completionHandler:) method refreshes the given OAuth2 token. The refreshed token (or the error during the process) is received in the completion handler:

authenticator.refresh(token: expiredToken) { token, error in
    if let error = error {
        // If there was an error during the authentication flow then the error can be handled here.
        // Request towards OAuth 2 protected resources will fail without a successful OAuth2 authentication and token acquisition.
        return
    }
    // When the error property is nil that means the OAuth 2 authentication was successful and a OAuth 2 token is acquired.
    // Store the acquired OAuth 2 token and add its access token to all HTTP request directed to OAuth 2 protected resources.
}

Cancelling authentication

An ongoing authentication flow can be cancelled. The ongoing authentication will receive a OAuth2Error.cancelled error in the completionHandler. This method does nothing if there was no ongoing authentication.

authenticator.cancelAuthentication()

Using the default web view presenter

Authorization Code Grant

The default web view presenter for OAuth2Authenticator is the SFSafariViewControllerPresenter in which the login page is presented. The OAuth2Authenticator needs to get the event of redirection to the redirectURL defined in the OAuth2AuthenticationParameters. For this you need to implement the AppDelegate’s func application(app:open:options:) method, and forward the parameters to the AppDelegateDispatcher.

class AppDelegate: UIResponder, UIApplicationDelegate {
    ...

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        return AppDelegateDispatcher.application(app, open: url, options: options)
    }

    ...
}

In order to make it work you need two things:

  • First, set up an URL scheme in your application project settings (project settings -> Info -> URL Types -> URL Schemes). In the Info.plist file it should look like this:
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>com.company.application.oauth</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>com.company.application.oauth</string>
        </array>
    </dict>
</array>

com.company.application.oauth is your application’s id

  • Second, configure the backend service to use the URL Scheme you defined in the redirect URL

Note: When using two different instances of OAuth2Authenticator (on different SAPURLSession instances) make sure you have configured unique URL schemes for each.

Using your own web view presenter implementation

Authorization Code Grant

The OAuth2 authentication requires a web view login page, which requires the authenticator to put a web view on the top of the current view hierarchy. The authenticator uses the SFSafariViewControllerPresenting protocol to achieve this which is implemented by default. If you want to use your own implementation, you can implement on of the following protocols and pass it in the initializer:

Example for implementing UIWebViewPresenting:

class MyViewController: UIWebViewPresenting {
    weak var delegate: WebViewPresenterDelegate?
    weak var webViewDelegate: SAPUIWebViewDelegate?

    func presentWebView(completionHandler: @escaping (UIWebView?, Error?) -> Void) {
        // TODO:
        // - acquire reference to `UIViewController`
        // - present the view controller which contains a `UIWebView`
        // - pass the `UIWebView` instance to the completionHandler
        // - pass an `Error` instance to the completionHandler if there was an error during presentation
    }

    func dismissWebView() {
        // TODO:
        // - acquire reference to presented `UIViewController` (or the presenting view controller)
        // - dismiss the presented view controller
    }
}

let presenter: WebViewPresenting = MyViewController()
let authenticator = OAuth2Authenticator(using: <#OAuth2AuthenticationParameters#>, urlSession: <#SAPURLSession#>, webViewPresenter: presenter)

From this point forward the authenticator uses the newly set presenter to load the web view login page.

All calls to the web view presenter inside the authenticator happen on the main thread. You must call the completionHandler of your web view presenter implementation on the main thread!

Make sure you call the completionHandler with the applicable objects when finished with the presentation.

The OAuth2Authenticator provides OAuth authentication and authorization functionality to your project and allows authentication of users without intercepting any secure workflows.

It is rare for a developer to access the OAuth2Authenticator directly — generally, the OAuth2Observer should be used. This component supports:

Sample code to assemble the authenticator can be found on the initializer’s API doc.

This sample code demonstrates an authentication flow:

authenticator.authenticate { token, error in
    if let error = error {
        // If there was an error during the authentication flow then the error can be handled here.
        // Request towards OAuth 2 protected resources will fail without a successful OAuth2 authentication and token acquisition.
        return
    }
    // When the error property is nil that means the OAuth 2 authentication was successful and a OAuth 2 token is acquired.
    // Store the acquired OAuth 2 token and add its access token to all HTTP request directed to OAuth 2 protected resources.
}

This sample code demonstrates a refresh flow:

authenticator.refresh(token: expiredToken) { token, error in
    if let error = error {
        // If there was an error during the authentication flow then the error can be handled here.
        // Request towards OAuth 2 protected resources will fail without a successful OAuth2 authentication and token acquisition.
        return
    }
    // When the error property is nil that means the OAuth 2 authentication was successful and a OAuth 2 token is acquired.
    // Store the acquired OAuth 2 token and add its access token to all HTTP request directed to OAuth 2 protected resources.
}
  • Default SAPURLSession instance used to retrieve and refresh OAuth 2.0 tokens. Has a session description: OAuth 2.0

    Declaration

    Swift

    public static func defaultSAPURLSession() -> SAPURLSession

    Return Value

    SAPURLSession instance.

  • Starts the OAuth 2.0 authentication process. When using the Authorization Code Grant type, this method will present a web view on the main thread.

    Important: Make sure you have a strong reference to the object until the completionHandler is called!

    Declaration

    Swift

    public func authenticate(completionHandler: @escaping (_ token: OAuth2Token?, _ error: Error?) -> Void)

    Parameters

    completionHandler

    The completion handler that gets invoked at the end of the authentication process.

    token

    The obtained OAuth2Token if the process was successful, otherwise nil.

    error

    The Error happened during the process if there was any, otherwise nil.

  • Refreshes the given OAuth 2.0 token.

    Important: Make sure you have a strong reference to the object until the completionHandler is called!

    Declaration

    Swift

    public func refresh(token: OAuth2Token, completionHandler: @escaping (_ newToken: OAuth2Token?, _ error: Error?) -> Void)

    Parameters

    token

    the OAuth2Token to be refreshed

    completionHandler

    The completion handler that gets invoked at the end of the refresh process.

    newToken

    The refreshed OAuth2Token if the process was successful, otherwise nil.

    error

    The Error happened during the process if there was any, otherwise nil.

  • Cancels the ongoing authentication process. The authenticate method’s completionHandler will be invoked with OAuth2Error.cancelled error. Does nothing if there was no ongoing authentication.

    Declaration

    Swift

    public func cancelAuthentication()