Moving Your Android App off NTLM: Kerberos SSO with the Hypergate SDK

Android bugdroid and Cerberus mascots beside a code window with the com.hypergate:sdk dependency

Lukas Schönbächler · July 2026 · 9 min read

Why this matters

  • NTLM is on a deadline: NTLMv1 gets blocked in October 2026 (KB5066470) and network NTLM goes away with the next Windows Server LTSC. Android apps that still authenticate with NTLM need a replacement, and that replacement is Kerberos.
  • The Hypergate SDK (com.hypergate:sdk on Maven Central) adds SPNEGO negotiate tokens to any Android app, so your app answers with Kerberos instead of NTLM.
  • Two integration paths: WebViews authenticate with zero code, native HTTP requests need a token call and one header.
  • Silent variants deliver tokens to background workers and sync adapters without ever showing UI.
  • Configuration is pushed through managed configurations, so it works with all major MDM/EMM/UEM platforms.

Microsoft is dismantling legacy authentication piece by piece. NTLM audit events shipped with Windows 11 24H2 and Server 2025, NTLMv1 single sign-on gets blocked in October 2026 (KB5066470), and network NTLM disappears entirely with the next Windows Server LTSC. We covered what the deprecation means for mobile fleets in Microsoft’s plan to disable NTLM, and its sibling cleanup, the Kerberos RC4 removal, already reached its point of no return this month.

The server side of that migration is well documented. The client side is where mobile teams get stuck: every in-house Android app that still answers an NTLM challenge, whether through a WebView or its own HTTP stack, has to start sending Kerberos negotiate tokens instead. Users will not accept the alternative, which is typing domain passwords into phones. This post walks through closing that gap with the Hypergate SDK: what it does, how to set it up, and which of its request methods to use where.

What the SDK actually does

The SDK is the app-side piece of a Kerberos single sign-on flow. The user’s Kerberos identity lives in a Kerberos authenticator app on the device, such as Hypergate Authenticator, exposed through the standard Android account framework. When your app needs to call a Kerberized service, the SDK asks that account for a SPNEGO negotiate token for the service’s principal name (for example [email protected]) and hands it back to you as a string. You place it in the Authorization: Negotiate header and the request is authenticated.

Your app never sees a password and never stores credentials. All it does is request tokens. That is also what makes it the natural landing spot for an NTLM migration: NTLM’s design revolves around password-derived challenge responses, exactly the material attackers relay and crack, and a negotiate token contains no password material at all. Which servers are even allowed to receive tokens is not decided in code either: IT controls it centrally through a managed configuration allowlist.

Two Ways to Integrate the Hypergate SDK WebView path (zero code) Add dependency + manifest meta-data EMM pushes managed configuration Every WebView authenticates transparently Native path (a few lines) Request token for the service principal (SPN) Set Authorization: Negotiate header Works with any HTTP client (OkHttp, Volley, Retrofit, …)

Figure 1: Hybrid and WebView apps are done after setup. Native apps add one token call per request.

Setup: one dependency, one manifest entry

Add the SDK from Maven Central to your app’s build.gradle (check Maven Central for the latest version):

dependencies {
    implementation "com.hypergate:sdk:1.6.0"
}

If your application does not define its own app restrictions, reference the SDK’s restrictions file in your AndroidManifest.xml:

<application>
    <meta-data
        android:name="android.content.APP_RESTRICTIONS"
        android:resource="@xml/hypergate_sdk_restrictions" />
</application>

If you already ship your own restrictions, copy the entries from hypergate_sdk_restrictions.xml into your file instead. Either way, your app now exposes managed configurations that IT sets in the EMM console:

  • Account type for HTTP Negotiate authentication: which account type answers authentication challenges. For Hypergate this is ch.papers.hypergate.
  • Authentication server allowlist: which servers may request tokens. Either a wildcard (*) or an explicit list of domains.
  • Whether NTLMv2 authentication is enabled: a WebView fallback toggle that is unrelated to Hypergate itself. If your goal is an NTLM-free app, leave it disabled so the WebView answers Negotiate challenges only.

That central allowlist is worth pausing on: it means security policy travels with the device management layer, not with app releases. Tightening the list of token-receiving servers never requires you to ship an update.

Path 1: WebViews, zero code

If your app is hybrid (Cordova, Capacitor, or similar) or renders internal services in a WebView, you are already done. With the dependency and the restrictions in place, every WebView in your project answers HTTP Negotiate challenges transparently, for page loads and AJAX calls alike. There is no interceptor to write and no token to manage; the user opens the screen and is signed in.

For Cordova specifically there is a ready-made plugin and a sample app. The one case where WebView magic does not apply: a hybrid app that performs its HTTP requests through a native plugin rather than the WebView stack. That plugin is a native client, so it follows path 2.

Path 2: native token requests

For native HTTP calls you request a token yourself. The API is one method in four flavors, along two axes: synchronous versus asynchronous, and regular versus silent. The regular variants take an Activity and may show UI if user interaction is needed, for example an account picker or a consent screen. The silent variants take a plain Context, never show UI, and fail instead, which is exactly what you want in a worker or sync adapter.

MethodBlockingTakesMay show UITypical caller
requestTokenSyncyesActivityyesinterceptor on a background thread
requestTokenAsyncnoActivityyesUI-driven flows
requestTokenSilentlySyncyesContextneverWorkManager, sync adapters
requestTokenSilentlyAsyncnoContextneverpush handlers, services

The synchronous call is a one-liner:

val token = Hypergate.requestTokenSync(activity, "[email protected]")

// or from a background component, without any UI:
val token = Hypergate.requestTokenSilentlySync(context, "[email protected]")

The asynchronous variant delivers the token or the exception to callbacks:

Hypergate.requestTokenAsync(activity, "[email protected]",
    { negotiateToken -> Log.d("TOKEN", negotiateToken) },
    { exception -> Log.d("ERROR", exception.message) })

In practice most apps wrap the token call once and forget about it. With OkHttp that wrapper is an interceptor:

internal class HypergateOkHttpInterceptor(
    private val activity: Activity
) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        val token = Hypergate.requestTokenSync(
            activity,
            "HTTP/${request.url.host}"
        )
        val authenticatedRequest = request.newBuilder()
            .addHeader("Authorization", "Negotiate ${token}")
            .build()

        return chain.proceed(authenticatedRequest)
    }
}

val client = OkHttpClient.Builder()
    .addInterceptor(HypergateOkHttpInterceptor(activity))
    .build()

Every request through this client is now authenticated. The same pattern applies to Volley (build the header map in getHeaders() and return it) or any other HTTP stack: request token, set header, send. If your app previously carried an NTLM library or hand-rolled challenge-response code for these calls, this interceptor is what replaces it.

Beyond the basics: bundles, round trips, account helpers

The requestToken* methods return only the token string, which covers the common case. If you need the full AccountManager result, the requestTokenBundle* counterparts return the raw Bundle, with the token under AccountManager.KEY_AUTHTOKEN. The bundle variants also accept two extra parameters for servers that negotiate over multiple round trips: incomingAuthToken, the token the server returned in the previous leg, and spnegoContext, an opaque identifier that carries the negotiation state between calls.

val bundle = Hypergate.requestTokenBundleSync(
    activity, "[email protected]", incomingAuthToken, spnegoContext
)
val token = bundle.getString(AccountManager.KEY_AUTHTOKEN, "")

A few helpers round out the API for building sensible onboarding:

// true if a Hypergate account is present on the device
Hypergate.hasAccount(context)

// all Hypergate accounts (honors the managed configuration)
val accounts: Array<Account> = Hypergate.getAccounts(context)

// system intent to let the user pick or add an account
startActivity(Hypergate.getAccountChooserIntent(context))

Check hasAccount() at startup: if it returns false, the device is not enrolled or the authenticator is not provisioned yet, and you can show a meaningful message instead of letting the first request fail.

Which Method Do I Call? I need a negotiate token Can I show UI here? (is an Activity available?) no requestToken- SilentlySync / Async workers, sync adapters, push handlers yes Need the raw bundle or a multi-round-trip negotiation? yes requestToken- Bundle* variants incomingAuthToken + spnegoContext no requestTokenSync / Async the default choice

Figure 2: Picking the right variant. Background code takes the silent methods, multi-leg negotiations take the bundle methods, everything else takes the default pair.

Error handling

All failure callbacks deliver a HypergateException carrying two properties: a numeric code your app can switch on, and a verbose English message for logs. The one you will meet first is code 101, “no accounts found”. It usually does not mean the device has no Hypergate account; it means your app’s package name is missing from the discoverability list in the Hypergate managed configuration, so the account is invisible to your app. Add the package name in the EMM console and the same call succeeds.

Where this leaves you

Every legacy-authentication deadline lands in the same place: Kerberos with AES and, increasingly, certificates instead of passwords. Getting your Android apps there comes down to a small integration surface: a dependency, a manifest entry, and either nothing at all (WebViews) or one token call per request (native). Key handling and ticket negotiation stay inside the authenticator, where they belong. Your app requests tokens and IT controls policy through the EMM. When the October NTLMv1 block and the LTSC cutover arrive, your apps are already on the right side of them. The SDK source and full API documentation live in the GitHub repository.


Moving your apps off NTLM?

Hypergate Authenticator provides the Kerberos identity your apps consume through this SDK, on managed Android and iOS, with every major EMM and no NTLM anywhere in the path. Talk to us about your migration off legacy authentication.

Other Stories