Swift iOS App using HAAPI

Swift iOS App using HAAPI

Code Examples / mobile-integration

This tutorial shows how to run a code example that implements mobile OpenID Connect in a Swift App using the Hypermedia Authentication API (HAAPI). See also the API Driven Demo Client tutorial for an introductory guide that runs a simple web client.

Overview

The code sample consists of an initial Unauthenticated View used to trigger sign in, but before a login attempt is allowed, the app must cryptographically prove its identity using Client Attestation:

Unauthenticated View

The authentication workflow is then managed as a series of fast API requests that return JSON responses. The mobile app processes each API response to perform various actions including rendering of forms:

Login Form

Once authentication completes, the app presents its Authenticated View, to simulate a mobile app that works with access tokens to call APIs. The app can then easily perform other OAuth operations such as refreshing tokens and ending the authenticated session:

Authenticated View

Get the Code

First ensure that you are running Xcode 12.5 or later, then clone the GitHub repository and open its folder to view the Swift classes used:

iOS HAAPI Project

Setup

Configure a Mobile OAuth Client

The iOS HAAPI SDK article explains the details of how to configure an iOS hypermedia client. This includes configuration of Client Attestation settings for development, so that the app must prove its digital identity before it is allowed to attempt to authenticate end users.

The app’s OAuth settings are specified in the Settings/Profile module, which by default uses a mobile OAuth client named haapi-ios-dev-client. In this tutorial we are also using an internet SSL URL for the Identity Server:

private enum Constants {
    static let defaultAuthorizationEndpointURI = "https://ef5540f40573.eu.ngrok.io/oauth/v2/oauth-authorize"
    static let defaultTokenEndpointURI = "https://ef5540f40573.eu.ngrok.io/oauth/v2/oauth-token"
    static let defaultName = "Default"
    static let defaultClientId = "haapi-ios-dev-client"
    static let defaultBaseURLString = "https://ef5540f40573.eu.ngrok.io"
    static let defaultMetaBaseURLString = "https://ef5540f40573.eu.ngrok.io/oauth/v2/oauth-anonymous"
}

Connectivity from Mobile Devices

When running the Curity Identity Server on your development computer, it can be useful to use ngrok to create a trusted SSL connection from an emulator or device. This can be done via the following simple script which assumes that the app connects to OAuth endpoints using the default port of 8443:

ngrok http 8443 -log=stdout &
sleep 5
BASE_URL=$(curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[] | select(.proto == "https") | .public_url')
echo $BASE_URL

The generated URL such as https://ef5540f40573.eu.ngrok.io can then be configured as the Base URL in the Admin UI. Also set the protocol under Deployments to HTTP, as discussed in the Exposing Curity Using ngrok article.

Mobile ngrok

Run the App

Once the above configuration is complete you are ready to run the app and use whichever authenticators and users you have configured in the Curity Identity Server. In the following sections we will explain the key technical behavior implemented in the code example.

HAAPI Integration

Integrating the Hypermedia Authentication API is done in three layers summarized below. Curity will be providing libraries for the controller layer in the near future, so that integration requires very little code:

LayerImplemented ByDescription
DriverCurity LibraryProcessing of low level messages used for client attestation to prove the app’s identity
ControllerYour AppProcessing HAAPI responses, including form rendering, following redirects and polling the server
ViewYour AppOverriding the defaults and customizing presentation to take full control over the login user experience

Client Attestation Overview

When authentication is first triggered, the app sends a number of low level messages to prove it is the genuine app, based on the digital identity with which the app is signed and delivered to the App Store.

This must be done before the app is allowed to attempt to authenticate the user. The main request to prove the client’s identity is a client assertion request with the following parameters:

Form ParameterExample Value
client_idios-haapi-client
scopeurn:se:curity:scopes:haapi
grant_typeclient_credentials
client_assertion_typeurn:se:curity:attestation:client
client_assertioneyJraWQi…paTubjbwReZ0A

The server verifies these details and returns a JWT access token in a JSON response that must be sent with subsequent requests in the authentication workflow, along with a separate proof of possession token. See the HAAPI Whitepaper for further details on security.

Response FieldExample Value
access_tokeneyJraWQi…JUHpZbxbstkK41RJhA7vw
scopeurn:se:curity:scopes:haapi
token_typeDPoP

Authentication Requests

Once the client is attested it can start authenticating the user, which is done using standard OpenID Connect parameters, but with a custom accept header to indicate that the client wants to receive hypermedia responses:

 GET /oauth/v2/oauth-authorize?client_id=ios-haapi-client&response_type=code&redirect_uri=haapi:start
 accept: application/vnd.auth+json

Various types of response such as the following may then be returned, in a similar manner to standard OpenID Connect responses. The data is received as JSON rather than HTML, so that it can be processed by non browser clients:

  • Forms to be rendered
  • Redirects to be followed
  • Instructions to open the system browser
  • Polling responses
  • Error Responses

A sample response when running the code example with an HTML Form authenticator is shown below. Note that the main response is a form to submit a username and password, but there are also links to related operations for user self sign up and account recovery:

{
    "links": [
        {
            "href": "/authn/authentication/Username-Password/forgot-password",
            "rel": "forgot-password",
            "title": "Forgot your password?"
        },
        {
            "href": "/authn/authentication/Username-Password/forgot-account-id",
            "rel": "forgot-account-id",
            "title": "Forgot your username?"
        },
        {
            "href": "/authn/registration/Username-Password",
            "rel": "register-create",
            "title": "Create account"
        }
    ],
    "metadata": {
        "viewName": "authenticator/html-form/authenticate/get"
    },
    "type": "authentication-step",
    "actions": [
        {
            "template": "form",
            "kind": "login",
            "title": "Login",
            "model": {
                "href": "/authn/authentication/Username-Password",
                "method": "POST",
                "type": "application/x-www-form-urlencoded",
                "actionTitle": "Login",
                "fields": [
                    {
                        "name": "userName",
                        "type": "username",
                        "label": "Username"
                    },
                    {
                        "name": "password",
                        "type": "password",
                        "label": "Password"
                    }
                ]
            }
        }
    ]
}

Responses follow a generic API model and can therefore be processed in a generic way within the mobile app, using methods in the HaapiController class. The view classes then do the finer grained work such as rendering the set of fields within a form model:

private func processForm(_ form: FormModel,
                             isRedirect: Bool)
    {
        problemViewModel = nil
        self.form = form
        self.isRedirect = isRedirect

        let fields = form.fields.filter { !$0.isHidden }
        fieldViewModels = fields.map{ field in
            FieldViewModel(field: field)
        }
    }
}

Using and Refreshing Access Tokens

After authentication the client will use access tokens to call APIs in the standard way, by sending them as bearer tokens in the HTTP Authorization header. With our example settings, access tokens will expire every 15 minutes, after which the app will need to refresh the access token.

This can be done very easily with a simple refresh token grant request, by posting form URL encoded parameters such as these to the token endpoint, and this can be easily coded with any Swift HTTP client:

Form ParameterExample Value
grant_typerefresh_token
client_idios-haapi-client
refresh_token62a11202-4302-42e1-983e-b26362093b67

Logout

Since the mobile client uses the Hypermedia Authentication API and is cookieless, logout can be implemented in a very simple manner, by just removing tokens from the app and redirecting back to the Unauthenticated View. Authentication can then be run again, and when testing it is easy to sign in as a different user.

Conclusion

The Hypermedia Authentication API provides a number of benefits to mobile apps, in terms of both finanical grade security and login usability:

  • The app gets an improved login user experience
  • The Identity Server continues to dictate security
  • The authentication workflow can be updated without needing to redeploy the app
  • Only genuine mobile clients can attempt user authentication
  • Man In the Browser (MITB) risks are eliminated for most authentication methods

Keep up with our latest articles and how-tos RSS feeds.