OAuth2Observer

open class OAuth2Observer

OAuth2Observer


The use of this component is the SDK-suggested way of implementing OAuth 2 authentication in the application. The OAuth2Observer allows a transparent OAuth authentication for HTTP requests. It sets the OAuth HTTP header on requests before they are sent. Moreover, it can detect failures that occur during OAuth authentication and handle authentication failures. It uses an OAuth2Authentication instance to authenticate and to refresh tokens. For token storing the OAuth2TokenStore is used.

OAuth 2 is an open standard for authorization and enables applications to obtain controlled access to protected resources, for instance, a HTTP or OData service.

This component currently supports:

Usage

There are several combination of configurations how the OAuth authentication can be implemented using the SDK.

SDK-suggested way

The most common and sdk-suggested way to implement the authentication is the use of the convenience initializers. Only the OAuth2AuthenticationParameters and an instance of SAPURLSession is required beforehand.

Authorization Code Grant

// Create the wrapper with the server side configuration
let authorizationEndpointURL = URL(string: "<#URL String#>")!
let tokenEndpointURL = URL(string: "<#URL String#>")!
let tokenRedirectURL = URL(string: "<#URL String#>")!
let clientID = "<#Client ID hash#>"
let scopes = Set<String>()
let parameters = OAuth2AuthenticationParameters(authorizationEndpointURL: authorizationEndpointURL, clientID: clientID, redirectURL: tokenRedirectURL, tokenEndpointURL: tokenEndpointURL, requestingScopes: scopes)

// Create the OAuth2Observer using the convenience initializer
let observer = OAuth2Observer(authenticationParameters: parameters, tokenStore: self)

// Register the newly created observer
sapURLSession.register(observer)

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 observer = OAuth2Observer(clientCredentialsAuthenticationParameters: parameters, tokenStore: OAuth2TokenStorage())

// Register the newly created observer
sapURLSession.register(observer)

The example above uses the default OAuth2TokenStorage shipped with the SDK. It is assumed to have a sapURLSession which is an instance of SAPURLSession.

By following this method, the OAuth2Authenticator is implicitly instantiated with a new instance of SAPURLSession.

Note: The OAuth2Authenticator uses SFSafariViewController by default. See more at Using the default web view presenter section of the OAuth2Authenticator.

Customization

There can be scenarios where the SDK supplied convenience implementation is not sufficient. The SDK supports multiple level of customizations. You can find examples at the Authentication section.

Implementing OAuth2TokenStore

A default implementation of the protocol is available in the SDK: OAuth2TokenStorage.

This sample code demonstrates what a very basic (in-memory) store implementation might look like:

private let accessQueue = DispatchQueue(label: "OAuth2TokenAccessQueue", attributes: .concurrent)
private var tokens = [String: OAuth2Token]()

public func storeToken(_ token: OAuth2Token, for url: URL) {
    accessQueue.sync(flags: .barrier) {
        self.tokens[url.host!] = token
    }
}

public func token(for url: URL) -> OAuth2Token? {
    return accessQueue.sync {
        return self.tokens[url.host!]
    }
}

public func deleteToken(for url: URL) {
    accessQueue.sync(flags: .barrier) {
        self.tokens[url.host!] = nil
    }
}

Triggering

By default the OAuth2Observer reacts to the HTTP 401 response status code, and to the HTTP 400 with X-SMP-AUTHENTICATION-STATUS: 1000 header which may be sent by SAP Cloud Platform Mobile Services, if the authorization token is missing or wrong.

When custom challenge decision logic is required, you can override the OAuth2 observer’s external challenge decision making method. The following example shows how you can override the default logic:

class CustomOAuth2Observer: OAuth2Observer {
    override open func isChallenge(dataTask: SAPURLSessionTask, response: URLResponse) -> Bool {
        // Check the response, and decide if it is an OAuth2 challenge or not.
        // Return true in case of an identified OAuth2 challenge
        return true
    }
}

Failure handling

Authentication failures can happen for multiple reasons:

  • There is no OAuth2 token present on the request.
  • The sent OAuth2 token is invalid or expired.

The OAuth2Observer supports two ways for handling an authentication failure:

  1. Authenticate
  2. Refresh token

After the first failure, if there is no token in the OAuth2TokenStore, then the authentication process starts. After a successful authentication, the original request is resent.

If there is a token in the OAuth2TokenStore, then the refresh process starts. After a successful refresh, the original request is resent.

Multiple requests

If multiple requests try to authenticate at once, only the first proceeds and the others wait for the result. In case of authentication failure, the next request in the queue tries to authenticate again. It is the caller’s responsibility to cancel all other active requests in case of authentication failure.

Note - regarding the main thread

The observer is aware of the application state, and does not allow to run UI-required authentication in background state. It uses the main thread synchronously to read the UIApplication.shared.applicationState property.

Do not use the main thread to wait (block) for a network request which goes through this observer!

The use of this component is the SDK-suggested way of implementing OAuth 2.0 authentication in the application. The OAuth2Observer allows a transparent OAuth authentication for HTTP requests. It sets the OAuth HTTP header on requests before they are sent. Moreover, it can detect failures that occur during OAuth authentication and handle authentication failures. It uses an OAuth2Authentication instance to authenticate and to refresh tokens. For token storing the OAuth2TokenStore is used.

This component supports:

Example use:

// Create the wrapper with the server side configuration
let authorizationEndpointURL = URL(string: "<#URL String#>")!
let tokenEndpointURL = URL(string: "<#URL String#>")!
let tokenRedirectURL = URL(string: "<#URL String#>")!
let clientID = "<#Client ID hash#>"
let parameters = OAuth2AuthenticationParameters(authorizationEndpointURL: authorizationEndpointURL, clientID: clientID, redirectURL: tokenRedirectURL, tokenEndpointURL: tokenEndpointURL)

// Create the OAuth2Observer using the convenience initializer
let observer = OAuth2Observer(authenticationParameters: parameters, tokenStore: self)

// Register the newly created observer
sapURLSession.register(observer)

The example above assumes that the class in which it is executed implements the OAuth2TokenStore protocol, therefore it can pass self as the tokenStore. It is also assumed to have a sapURLSession which is an instance of SAPURLSession.

  • when set the observer calls this handler after a new authentication but before the authentication process finishes and all other requests continues

    Declaration

    Swift

    public var authenticationHandler: AuthenticationHandling?
  • Instantiates the observer with the given authenticator and token store.

    Declaration

    Swift

    public init(authenticator: OAuth2Authentication, tokenStore: OAuth2TokenStore)

    Parameters

    authenticator

    the OAuth2Authentication instance to be used when encountered with an authentication challenge.

    tokenStore

    the OAuth2TokenStore implementation which can provide and store the obtained token.

  • Instantiates the observer based on the authentication parameters. You can also provide an OAuth2TokenStore or use the default value. The authenticator is implicitly created with a new instance of SAPURLSession.

    Declaration

    Swift

    public convenience init(authenticationParameters: OAuth2AuthenticationParameters, tokenStore: OAuth2TokenStore)

    Parameters

    authenticationParameters

    The necessary parameters to authenticate and refresh a token. Parameters contain server related information.

    tokenStore

    the OAuth2TokenStore implementation which can provide and store the obtained token.

  • Instantiates the observer based on the authentication parameters. You can also provide an OAuth2TokenStore or use the default value. The authenticator is implicitly created with a new instance of SAPURLSession.

    Declaration

    Swift

    public convenience init(clientCredentialsAuthenticationParameters authenticationParameters: OAuth2ClientCredentialsAuthenticationParameters, tokenStore: OAuth2TokenStore)

    Parameters

    authenticationParameters

    The necessary parameters to authenticate and refresh a token. Parameters contain server related information.

    tokenStore

    the OAuth2TokenStore implementation which can provide and store the obtained token.

  • External OAuth 2.0 challenge decision logic. The default behaviour checks the status code of the response. When the status code is 400 or 401, the authentication (or refresh) flow will start, otherwise this observer is skipped. The method can be overriden if custom challenge decision logic is needed. The OAuth2 observer will launch the authentication (or refresh) flow if this method returns true.

    Declaration

    Swift

    open func isChallenge(dataTask: SAPURLSessionTask, response: URLResponse) -> Bool

    Parameters

    dataTask

    the resumed SAPURLSessionTask

    response

    the received URLResponse

    Return Value

    True is the response indicates an OAuth2 challenge, False otherwise