Skip to content

Setup⚓︎

In this section, we will explain how to access the Mobile SDK, import it into your project and initialise it. Furthermore, since the Mobile SDK works asynchronously, you will need to set up listening for callbacks from it. After these steps, you can proceed to enrol your first account.

Minimal Requirements⚓︎

The Mobile SDKs require Android 6.0 (or above) and iOS 12.0 (or above).

Access to the Mobile SDKs, Docs & Example Apps⚓︎

The Android and iOS Mobile SDKs are available through our GitLab instance at https://git.nextauth.com/. If you do not have access, please contact support.

Each Mobile SDK comes with a full reference of their public APIs. For Android, this is in the form of Javadoc, which is distributed inside a Maven package, together with the SDK. For iOS, these docs are part of the Git repository under the docs directory.

Both SDKs work with releases, where you can also find the release notes. Through the GitLab interface, you can subscribe to email notification for new releases.

In addition to both SDKs there are also two example Acme apps, one for Android and one for iOS, that you can use as a starting point.

Importing the Mobile SDK⚓︎

Android⚓︎

First, in our GitLab interface, generate a personal access token with the api scope. Next, create a file ~/.gradle/gradle.properties with the following content.

gitLabPrivateToken={PERSONAL_ACCESS_TOKEN}

Then, add the following to your project's build.gradle script.

allprojects {
    repositories {
        ...
        maven {
            url "https://git.nextauth.com/api/v4/projects/3/packages/maven"
            name "GitLab"
            credentials(HttpHeaderCredentials) {
                name = 'Private-Token'
                value = gitLabPrivateToken
            }
            authentication {
                header(HttpHeaderAuthentication)
            }
        }
        ...
    }
}

Continue by declaring the nextAuth android SDK dependency in your module's build.gradle script.

dependencies {
    ...
    implementation 'com.nextauth.android:nextauth:1.1.6'
    ...
}

Finally, add the following features and permissions in the AndroidManifest.xml of your app.

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

// needed for scanning QR codes
<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />
<uses-feature
    android:name="android.hardware.camera.front"
    android:required="false" />

<uses-permission android:name="android.permission.CAMERA" />

// needed for biometrics
<uses-feature
    android:name="android.hardware.fingerprint"
    android:required="false" />

<uses-permission android:name="android.permission.USE_BIOMETRIC" />

iOS⚓︎

Our iOS Mobile SDK is distributed as a CocoaPod. You therefore need to add the following lines to your Podfile.

pod 'NextAuth-Debug', git: 'git@git.nextauth.com:sdks/ios.git', tag: '1.1.3', configuration: 'Debug'
pod 'NextAuth-Release', git: 'git@git.nextauth.com:sdks/ios.git', tag: '1.1.3', configuration: 'Release'

Info

We provide debug and release versions of the iOS Mobile SDK. The release version includes runtime protection technologies and does not allow for debuggers to be attached to your app if it contains (and initialises) the Mobile SDK. Take care that you only use the debug version of the Mobile SDK while developing your app (as you would otherwise not be able to debug your own app). However, you must use the release version of the Mobile SDK for apps that are released. By importing the iOS Mobile SDK as follows, this is automatically taken care off:

#if canImport(NextAuth_Release)
    import NextAuth_Release
#elseif canImport(NextAuth_Debug)
    import NextAuth_Debug
#endif

Initialising the Mobile SDK⚓︎

Android⚓︎

Before initialising the SDK, make sure to have a nextauth.json file in the assets folder of the app. This file should at least contain the signed configuration string provided by nextAuth. See here for other optional configuration parameters that can be added to this file.

{
    "config": ""
}

The SDK operates as a singleton and should thus be initialised at the application level. Create your custom MyApplication extending android.app.Application and initialise the SDK by calling the NextAuth.init(Context) function passing the application context.

import com.nextauth.android.NextAuth;

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // Initialise nextAuth Mobile SDK
        NextAuth.init(getApplicationContext());
    }

}

iOS⚓︎

Once the Pods have been installed, you should include a Constants.plist file in your project with the following contents. Ensure that it is added to the main application bundle. We have included a standard configuration below.

Warning

Don't forget to set the values of the config parameter. This is critical for the app to operate correctly. The other parameters allow you to customise the app's behaviour.

Warning

When you want to run the app on-device, you have to create an App Group in Apple's Developer portal, it will typically have the form group.com.nextauth.Authenticator. Once it has been created, don't forget to grant the app's Bundle ID the capability to access this group, update your provisioning profiles, and set the appGroup parameter.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NAConfig</key>
    <string></string>
</dict>
</plist>

The final task to take care of on the iOS side, is to add the following value to the app's Info.plist file.

<key>NAConstantsFile</key>
<string>$(NA_CONSTANTS_FILE)</string>

In order for this to work, you should define the NA_CONSTANTS_FILE build variable in the User-Defined section of the app's Build Settings. The new User-Defined value NA_CONSTANTS_FILE should be defined with values Constants. This setup allows you to easily vary the referenced Constants.plist file for different app configurations.

Setting up Callback Listeners⚓︎

There are two type of callbacks: those that require user interaction (e.g., confirm login, input the second factor, alerts...) and those for updating the UI (e.g. second factor input result, session that is logged in, session that is logged out, account that is deleted...).

The general idea is to listen for those that require user interaction at the top level (application on Android, top level view controller on iOS) and start specific activities (Android) / views (iOS) for capturing the user's input. Listening for UI updates happens in the specific activities/views.

Android⚓︎

To listen for callbacks, your class need to implement the NextAuthObserver, add itself as an observer NextAuth.addObserver(this), and implement the onCallback(Callback callback) function.

Callbacks to Start User Interaction⚓︎

Assuming your application has a specific ConfirmActivity and SecondFactorActivity, you can implement the top level user interaction by extending myApplication as follows:

import com.nextauth.android.callback.Callback;
import com.nextauth.android.callback.ConfirmEnrol;
import com.nextauth.android.callback.ConfirmLogin;
import com.nextauth.android.callback.SecondFactor;
import com.nextauth.android.callback.UserErrorMessage;
import com.nextauth.android.NextAuth;
import com.nextauth.android.SecondFactorInfo;

public class MyApplication extends Application implements NextAuthObserver {

    @Override
    public void onCreate() {
        super.onCreate();
        // Initialise nextAuth Mobile SDK
        NextAuth nextAuth = NextAuth.init(getApplicationContext());
        // Register observer
        nextAuth.addObserver(this);
    }

    // Handle callbacks
    @Override
    public void onCallback(Callback callback) {
         if (callback instanceof ConfirmLogin || callback instanceof ConfirmEnrol) {
            // Start ConfirmActivity
            Intent intent = new Intent(this, ConfirmActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
        if (callback instanceof SecondFactor) {
            // Start SecondFactorActivity (only if it still requires user input)
            SecondFactorInfo secondFactorInfo = ((SecondFactor) callback).secondFactorInfo;
            if (!((SecondFactor) callback).stopped && (secondFactorInfo == null || secondFactorInfo.getResult() != SecondFactorInfo.Result.SUCCESS)) {
                Intent intent = new Intent(this, SecondFactorActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        }
        if (callback instanceof UserErrorMessage) {
            // Show Toast message containing the error
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(() -> {
                Toast.makeText(this, ((UserErrorMessage) callback).errorMessage, Toast.LENGTH_LONG).show();
            });
        }
    }

}

Note that we did not pass a callback to the ConfirmActivity and SecondFactorActivity, when starting those. Instead, we retrieve the callback in the onCreate() method of those activities by calling NextAuth.getUserInteraction().

@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
    super.onCreate(savedInstanceState, persistentState);
    // Retrieve current user interaction callback
    Callback callback = NextAuth.getNextAuth().getUserInteraction();
    // TODO: Implement functionality to handle the callback
}

Tip

Android apps typically have multiple entry points, the most prominent one being the main activity (the activity in the AndroidManifest with <action android:name="android.intent.action.MAIN" />). We recommend calling the NextAuth.getUserInteraction() function in every entry point's onResume() and starting the ConfirmActivity or SecondFactorActivity, if needed. This way, when the user needs, e.g.n, to input their pin/biometric, then switches to another app, and finally goes back to your app through the app launcher, the user will not end up in the main activity, but will be able to continue inputting their pin/biometric.

@Override
protected void onResume() {
    super.onResume();
    // Retrieve the current user interaction callback
    Callback callback = NextAuth.getNextAuth().getUserInteraction();
    if (callback instanceof ConfirmLogin || callback instanceof ConfirmEnrol) {
        // Start ConfirmActivity
        Intent intent = new Intent(this, ConfirmActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
    if (callback instanceof SecondFactor) {
        // Start SecondFactorActivity (only if it still requires user input)
        SecondFactorInfo secondFactorInfo = ((SecondFactor) callback).secondFactorInfo;
        if (!((SecondFactor) callback).stopped && (secondFactorInfo == null || secondFactorInfo.getResult() != SecondFactorInfo.Result.SUCCESS)) {
            Intent intent = new Intent(this, SecondFactorActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
    }
}

Tip

You can also add a nextAuthObserver to the QR code scanner/camera activity and finish this activity when receiving a ConfirmLogin or SecondFactor callback (which will be started by your application if implemented as above). This way, the QR code scanner/camera activity is removed from the stack and the user will not end up in it again.

Callbacks to Update the UI⚓︎

Interesting callbacks to listen for are SessionLogin, SessionStop, AccountDeleted, SessionReadyForUserInteraction.

Specifically for the ConfirmActivity, it should listen for a SessionStop callback and upon receiving it finish the ConfirmActivity (if the SessionStop is for the same session).

private Session session;

@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
    super.onCreate(savedInstanceState, persistentState);
    // Retrieve current user interaction callback
    Callback callback = NextAuth.getNextAuth().getUserInteraction();
    if (callback instanceof ConfirmLogin) {
        // Set session
        session = ((ConfirmLogin) callback).session;
    } else {
        if (callback instanceof ConfirmEnrol) {
            // Set session
            session = ((ConfirmEnrol) callback).session;
        } else {
            // The current user interaction is neither ConfirmLogin or ConfirmEnrol, finish this activity
            finish();
        }
    }
}

 @Override
 public void onCallback(Callback callback){
    // Finish the activity when the session, for which the activity was started, is stopped
    if (callback instanceof SessionStop && Arrays.equals(((SessionStop) callback).session.getUid(), session.getUid())) {
        finish();
    }
}

Specifically for the SecondFactorActivity, it should listen for SecondFactor callbacks telling it what to show to the user or when to finish the SecondFactorActivity (upon receiving a SecondFactor callback with stopped = true or SUCCESS - when no more user interaction is required).

@Override
public void onCallback(Callback callback) {
    if (callback instanceof SecondFactor) {
        SecondFactorInfo secondFactorInfo = ((SecondFactor) callback).secondFactorInfo;
        boolean stopped = ((SecondFactor) callback).stoppped
        if (callback.stopped || (secondFactorInfo != null && secondFactorInfo.getResult() == SecondFactorInfo.Result.SUCCESS)) {
            finish();
        } else {
            // TODO: Implement logic for getting the required user input
        }
    }
}

iOS⚓︎

On iOS, we rely on two different update mechanisms to inform you about the different types of state changes. First, you should implement the methods defined by the NextAuthDelegate protocol which is part of our Mobile SDK. We suggest conforming to this protocol in a high-level class which has the ability to add and remove view controllers to and from the view hierarchy. Second, the Mobile SDK also issues local notifications you can subscribe to in order to receive account and sessions updates and trigger relevant actions (e.g., updating a view showing all active sessions).

After implementing the protocol, don't forgot to assign an instance of the conforming class to the delegate property of the Mobile SDK's singleton.

// MARK: - UIViewController

override func viewDidLoad() {
    super.viewDidLoad()
    NextAuth.default.delegate = self
}

In our reference Acme app, MainViewController.swift conforms to the NextAuthDelegate protocol.

Callbacks to Start User Interaction⚓︎

Before continuining, ensure that you have implemented the required views to handle logins and enrols. You can find references for all three in the Acme app, respectively in the source files named LoginViewController.swift, EnrolViewController.swift, and SecondFactorViewController.swift. Finally, add the following to the class where you are implementing the NextAuthDelegate protocol.

// MARK: - NextAuthDelegate

func nextAuth(_ nextAuth: NextAuth, willConfirmLoginFor session: Session, from accounts: [Account], with sfNeeded: Bool) {
    let loginViewController = LoginViewController()
    loginViewController.accounts = accounts
    loginViewController.session = session
    loginViewController.sfNeeded = sfNeeded

    present(loginViewController, animated: true, completion: nil)
}

func nextAuth(_ nextAuth: NextAuth, willConfirmEnrolFor session: Session, with accounts: [Account], vash: UIImage?) {
    let enrolViewController = EnrolViewController()
    enrolViewController.accounts = accounts
    enrolViewController.session = session
    enrolViewController.vash = vash

    present(enrolViewController, animated: true, completion: nil)
}

func nextAuth(_ nextAuth: NextAuth, didStartSecondFactorWith info: NextAuth.SFInfo?, for session: Session?) {
    sfViewController.session = session
    sfViewController.sfInfo = info

    present(sfViewController, animated: true, completion: nil)
}

func nextAuth(_ nextAuth: NextAuth, didReturn error: Session.Error) {
    // TODO: Present alert to user
}

func nextAuth(_ nextAuth: NextAuth, didThrow panic: String) {
    // TODO: Optionally log the passed panic reason
    fatalError()
}

While the login and enrol view controllers should be relatively straightforward to implement, the one handling second factor events is somewhat more complicated. As you can see, two delegate methods have been defined. When a second factor flow is started, nextAuth(_:didStartSecondFactorWith:for:) will be invoked first and should display the second factor view controller.

Info

In contrast to the Mobile SDK for Android, we distinguish between non-fatal errors that should be displayed to the user and panics. We recommend presenting the former as a UIAlertController or banner. The latter can be logged both locally and remotely (e.g., to Sentry as is done in the Acme app) before crashing the app through Swift's fatalError() built-in.

Callbacks to Update the UI⚓︎

Since you might want to trigger multiple actions based on session or account updates, we opted to have the Mobile SDK post local notifications to the NotificationCenter. The names of all notifications have been made available through the NotificationNames struct. While in some cases you simply want to know that either a session or account changed, note that we also attach the Session or Account instance that triggered the notification to the posted notification. The code snippet below will get you started on subscribing to these notifications. For an example of how to use this in practice, please refer to SessionsViewController.swift of the Acme app.

// MARK: - Observers

@objc func handleAccountUpdateNotification(_ notification: NSNotification) {
    guard let account = notification.object as? Account else {
        return
    }

    // TODO: Implement required logic to process account update
}

@objc func handleAccountDeleteNotification(_ notification: NSNotification) {
    guard let account = notification.object as? Account else {
        return
    }

    // TODO: Implement required logic to process account delete
}

@objc func handleSessionUpdateNotification(_ notification: NSNotification) {
    guard let session = notification.object as? Session else {
        return
    }

    // TODO: Implement required logic to process session update
}

// MARK: - UIViewController

override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(self, selector: #selector(handleAccountUpdateNotification(_:)), name: NextAuth.NotificationNames.accountUpdate, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(handleAccountDeleteNotification(_:)), name: NextAuth.NotificationNames.accountDelete, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(handleSessionUpdateNotification(_:)), name: NextAuth.NotificationNames.sessionUpdate, object: nil)
}

In contrast to the local notification sent for session and account updates, second factor events rely are communicated through one of the delegate methods defined in our NextAuthDelegate protocol. Since the second factor view controller will be on-screen when these updates arrive, you should pass them onto that view controller instance. This is the reason why both our example func nextAuth(_:didStartSecondFactorWith:for:) implementation above and the snippet below reference the class property sfViewController.

// MARK: - NextAuthDelegate

func nextAuth(_ nextAuth: NextAuth, didUpdateSecondFactorWith info: NextAuth.SFInfo?, and stopped: Bool?) {
    sfViewController.sfInfo = info
    sfViewController.sfStopped = stopped
}

Once the second factor has been passed to the Mobile SDK, the verification result will be communicated back through the nextAuth(_:didUpdateSecondFactorWith:and:). We therefore recommend referencing the same instance of your second factor view controller in both methods. Furthermore, it should only be dismissed once the second callback confirms that the second factor has been verified successfully.