Guardian.swift iOS SDK

Guardian.swift allows you to integrate Auth0's Guardian multi-factor service in your own iOS app, transforming it into the second factor itself. Your users will get all the benefits of our frictionless multi-factor authentication from your app. To learn more, read Getting Started with Apple Push Notification Service on https://docs.aws.amazon.com.

Requirements

  • iOS 10+ and Swift 4.1 is required in order to use Guardian.

  • To use this SDK you have to configure your tenant's Guardian service with your own push notification credentials, otherwise, you would not receive any push notifications. To learn more, read Configure Push Notifications for MFA.

Install Guardian iOS SDK

CocoaPods

Guardian.swift is available through CocoaPods. To install it, add the following line to your Podfile:


curl --request PATCH \
  --url 'https://YOUR_DOMAIN/api/v2/tenants/settings' \
  --header 'authorization: Bearer API_ACCESS_TOKEN' \
  --header 'cache-control: no-cache' \
  --header 'content-type: application/json' \
  --data '{ "flags": { "use_scope_descriptions_for_consent": false } }'
var client = new RestClient("https://YOUR_DOMAIN/api/v2/tenants/settings");
var request = new RestRequest(Method.PATCH);
request.AddHeader("content-type", "application/json");
request.AddHeader("authorization", "Bearer API_ACCESS_TOKEN");
request.AddHeader("cache-control", "no-cache");
request.AddParameter("application/json", "{ \"flags\": { \"use_scope_descriptions_for_consent\": false } }", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_DOMAIN/api/v2/tenants/settings"

	payload := strings.NewReader("{ \"flags\": { \"use_scope_descriptions_for_consent\": false } }")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("content-type", "application/json")
	req.Header.Add("authorization", "Bearer API_ACCESS_TOKEN")
	req.Header.Add("cache-control", "no-cache")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.patch("https://YOUR_DOMAIN/api/v2/tenants/settings")
  .header("content-type", "application/json")
  .header("authorization", "Bearer API_ACCESS_TOKEN")
  .header("cache-control", "no-cache")
  .body("{ \"flags\": { \"use_scope_descriptions_for_consent\": false } }")
  .asString();
var axios = require("axios").default;

var options = {
  method: 'PATCH',
  url: 'https://YOUR_DOMAIN/api/v2/tenants/settings',
  headers: {
    'content-type': 'application/json',
    authorization: 'Bearer API_ACCESS_TOKEN',
    'cache-control': 'no-cache'
  },
  data: {flags: {use_scope_descriptions_for_consent: false}}
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"content-type": @"application/json",
                           @"authorization": @"Bearer API_ACCESS_TOKEN",
                           @"cache-control": @"no-cache" };
NSDictionary *parameters = @{ @"flags": @{ @"use_scope_descriptions_for_consent": @NO } };

NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/api/v2/tenants/settings"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"PATCH"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://YOUR_DOMAIN/api/v2/tenants/settings",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "PATCH",
  CURLOPT_POSTFIELDS => "{ \"flags\": { \"use_scope_descriptions_for_consent\": false } }",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer API_ACCESS_TOKEN",
    "cache-control: no-cache",
    "content-type: application/json"
  ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

payload = "{ \"flags\": { \"use_scope_descriptions_for_consent\": false } }"

headers = {
    'content-type': "application/json",
    'authorization': "Bearer API_ACCESS_TOKEN",
    'cache-control': "no-cache"
    }

conn.request("PATCH", "/YOUR_DOMAIN/api/v2/tenants/settings", payload, headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://YOUR_DOMAIN/api/v2/tenants/settings")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Patch.new(url)
request["content-type"] = 'application/json'
request["authorization"] = 'Bearer API_ACCESS_TOKEN'
request["cache-control"] = 'no-cache'
request.body = "{ \"flags\": { \"use_scope_descriptions_for_consent\": false } }"

response = http.request(request)
puts response.read_body
import Foundation

let headers = [
  "content-type": "application/json",
  "authorization": "Bearer API_ACCESS_TOKEN",
  "cache-control": "no-cache"
]
let parameters = ["flags": ["use_scope_descriptions_for_consent": false]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/api/v2/tenants/settings")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

Carthage

Add this line to your Cartfile:

github "auth0/Guardian.swift" ~> 1.0.0

Enable Guardian push notifications

  1. Go to Dashboard > Security > Multi-factor Auth.

    Auth0 Dashboard Security Multi-Factor Auth
  2. Toggle Push Notification on to enable it.

Configure SNS for native apps

For your native app to receive push notifications from Guardian, you will need to override the default SNS settings. See Configure Push Notifications for MFA for details.

Usage

Guardian is the core of the SDK. To use the SDK, import the library:

import Guardian

Then you'll need the Auth0 Guardian domain for your account:

let domain = "{YOUR_ACCOUNT_NAME}.guardian.auth0.com"

Enroll

An enrollment is a link between the second factor and an Auth0 account. When an account is enrolled you'll need it to provide the second factor required to verify the identity. If your app is not yet using push notifications or you're not familiar with it, see Apple Push Notification Service Overview for details.

For an enrollment you need the following information, besides your Guardian domain:

Variable Description
Enrollment URI Value encoded in the QR code scanned from Guardian Web Widget or your enrollment ticket sent to you in email or SMS.
APNS Token Apple APNS token for the device. It must be a string containing the 64 bytes (in hexidecimal format).
Key Pair An RSA (private/public) key pair used to assert your identity with Auth0 Guardian.

After you have the information, you can enroll your device:

Guardian
        .enroll(forDomain: "{YOUR_GUARDIAN_DOMAIN}",
                usingUri: "{ENROLLMENT_URI}",
                notificationToken: "{APNS_TOKEN}",
                signingKey: signingKey,
                verificationKey: verificationKey
                )
        .start { result in
            switch result {
            case .success(let enrolledDevice):
                // success, we have the enrollment device data available
            case .failure(let cause):
                // something failed, check cause to see what went wrong
            }
        }


On success you'll obtain the enrollment information, that should be secured stored in your application. This information includes the enrollment identifier, and the token for Guardian API associated to your device for updating or deleting your enrollment.

Signing and verification keys

Guardian.swift provides a convenience class to generate a signing key:

let signingKey = try DataRSAPrivateKey.new()

This key only exists in memory but you can obtain its Data representation and store securely in, for example, an encrypted SQLiteDB:

// Store data
let data = signingKey.data
// perform the storage

// Load from Storage
let loadedKey = try DataRSAPrivateKey(data: data)

But if you just want to store inside iOS Keychain:

let signingKey = try KeychainRSAPrivateKey.new(with: "com.myapp.mytag")

The above example creates a key and stores it automatically under the supplied tag. If you want to retrieve it, you can use the tag:

let signingKey = try KeychainRSAPrivateKey(tag: "com.myapp.mytag")

For the verification key, we can just obtain it from any SigningKey, for example:

let verificationKey = try signingKey.verificationKey()

Allow login requests

Once you have the enrollment in place, you will receive a push notification every time the user has to validate their identity with MFA. Guardian provides a method to parse the data received from APNs and return a Notification instance ready to be used.

if let notification = Guardian.notification(from: notificationPayload) {
    // we have received a Guardian push notification
}


Once you have the notification instance, you can easily allow the authentication request by using the allow method. You'll also need some information from the enrolled device that you obtained previously. In case you have more than one enrollment, you'll have to find the one that has the same id as the notification (the enrollmentId property).

When you have the information, device parameter is anything that implements the protocol AuthenticatedDevice:

struct Authenticator: Guardian.AuthenticationDevice {
    let signingKey: SigningKey
    let localIdentifier: String
}

Local identifier is the local id of the device, by default on enroll UIDevice.current.identifierForVendor. Then just call:

Guardian
        .authentication(forDomain: "{YOUR_GUARDIAN_DOMAIN}", device: device)
        .allow(notification: notification)
        .start { result in
            switch result {
            case .success:
                // the auth request was successfuly allowed
            case .failure(let cause):
                // something failed, check cause to see what went wrong
            }
        }


Reject login requests

To deny an authentication request call reject instead. You can also send an optional reject reason. The reject reason will appear in the Guardian logs.

Guardian
        .authentication(forDomain: "{YOUR_GUARDIAN_DOMAIN}", device: device)
        .reject(notification: notification)
        // or reject(notification: notification, withReason: "hacked")
        .start { result in
            switch result {
            case .success:
                // the auth request was successfuly rejected
            case .failure(let cause):
                // something failed, check cause to see what went wrong
            }
        }


Unenroll

If you want to delete an enrollment, for example, if you want to disable MFA, you can make the following request:

Guardian
        .api(forDomain: "{YOUR_GUARDIAN_DOMAIN}")
        .device(forEnrollmentId: "{USER_ENROLLMENT_ID}", token: "{ENROLLMENT_DEVICE_TOKEN}")
        .delete()
        .start { result in
            switch result {
            case .success:
                // success, the enrollment was deleted
            case .failure(let cause):
                // something failed, check cause to see what went wrong
            }
        }