Client Authentication (Driver Layer)#

When attestation cannot be used — older devices, simulators, or any Dynamic Client Registration fallback path — the client must authenticate by another method. Configure the chosen method when constructing HaapiTokenManager. For the concept and the three available methods, see Client Authentication.

Secret#

A static shared secret. Easy to set up; appropriate only for development or controlled environments because the secret is identical for every install.

let clientAuth = ClientAuthenticationMethodSecret("foo")

let haapiTokenManager = HaapiTokenManagerBuilder(
    tokenEndpoint: tokenEndpoint,
    clientId: clientId
)
.setClientAuthenticationMethod(method: clientAuth)
.build()
val authMethod = ClientAuthenticationMethodConfiguration.Secret(secret = "foo")

val haapiTokenManager = HaapiTokenManager.Builder(
    clientId = clientId,
    tokenEndpointUri = tokenEndpointUri
)
.setClientAuthenticationMethodConfiguration(authMethod)
.build()

Mutual TLS#

Mutual-TLS authentication: the client presents a certificate during the TLS handshake, and the identity server validates it against its configured trust store. In the other direction the SDK checks the server’s certificate before any request is sent; each platform tab below says how.

// Using a bundled PKCS12 keystore + server PEM:
let clientAuth = try ClientAuthenticationMethodMTLS(
    pkcs12Filename: "client.p12",
    pkcs12Passphrase: "passphrase",
    serverPEMFilename: "server.pem",
    isValidatingHostname: true,
    bundle: Bundle.main
)

// Or with pinned server public-key hash:
let clientAuthPinned = try ClientAuthenticationMethodMTLS(
    pkcs12Filename: "client.p12",
    pkcs12Passphrase: "passphrase",
    serverKeyPinnings: [
        KeyPinning(
            hostname: "idsvr.example.com",
            publicKeyHash: "Kjuy4mT3fbeDozRNP6rTjWRYmbs79Begb5Roq+DUu7s="
        ),
        KeyPinning(
            hostname: "idsvr.example.com",
            publicKeyHash: "<the next key, so a rotation does not break the app>"
        )
    ],
    bundle: Bundle.main
)

What is checked before the pin. The platform validates the server’s certificate chain first — signatures, validity period, and that the certificate names the connection host in its Subject Alternative Name — and only then is the pin compared against the certificates of that validated chain. A server whose root the device does not trust, such as a self-signed or private-CA server, is refused until its root is installed on the device; pinning never replaces that check. A refused server fails the request with the platform’s cancellation error and logs the reason under the Driver’s flow log tag.

Which certificate to pin. Any certificate of the validated chain can be pinned. The leaf key is the tightest pin but changes at every renewal; an intermediate or CA key survives leaf rotation. A pin is the base64 SHA-256 of the certificate’s SubjectPublicKeyInfo, the same string the Android SDK takes:

openssl x509 -in server.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

Configure two pins per host — the current key and the next — so a rotation does not break the app; a host with a single pin logs a warning, an empty pin list logs an error (every connection is then refused), and the SDK warns when the pinned certificate expires within 30, 14 and then 7 days. Supported server keys: RSA 2048, 3072 and 4096, EC P-256, P-384 and P-521. Hostnames are matched without regard to letter case.

isValidatingHostname applies to certificate pinning with serverPEMFilename and has no default. Pass true unless the certificate cannot name the host; false skips only the hostname match — the chain is still validated — and logs a warning. With the check off, only the server’s own certificate may be the pin: a pinned intermediate or CA certificate is not accepted, because it would admit any certificate that CA issued for any host. Key pinning always validates the hostname.

https is required. With mutual TLS configured, a request to a non-https URL is refused before anything is sent, with the error code insecure_transport and one line under the Driver’s http log tag, and HaapiTokenManagerBuilder.build() fails with the same code when the token endpoint is not https. See Upgrade — Server Key Pinning for what changed and how to prepare a deployment.

// Using bundled client keystore + server trust store:
val mtls = ClientAuthenticationMethodConfiguration.Mtls(
    clientKeyStore = myClientKeyStore,
    clientKeyStorePassword = "passphrase".toCharArray(),
    serverTrustStore = myServerTrustStore
)

// Or with pinned server public-key hash — pin the current key and the next
// key the server will rotate to, so a future rotation needs no app release:
val mtlsPinned = ClientAuthenticationMethodConfiguration.MtlsKeyHash(
    clientKeyStore = myClientKeyStore,
    clientKeyStorePassword = "passphrase".toCharArray(),
    serverKeyPinnings = setOf(
        ClientAuthenticationMethodConfiguration.MtlsKeyHash.KeyPinning(
            hostname = "idsvr.example.com",
            publicKeyHash = "Kjuy4mT3fbeDozRNP6rTjWRYmbs79Begb5Roq+CNwiG="
        ),
        ClientAuthenticationMethodConfiguration.MtlsKeyHash.KeyPinning(
            hostname = "idsvr.example.com",
            publicKeyHash = "L1sN0kkq8t1BR8nGZ+58vN5FQ4URtT/nRhrEUUwj9ZQ="
        )
    ),
    isValidatingHostname = true
)

publicKeyHash is the hash of a certificate in the server’s chain, usually the leaf. The platform validates the chain first, exactly as for any HTTPS connection; the pin is then required on one of the validated certificates, so pinning tightens platform trust and never replaces it. A server whose certificate authority the device does not trust is refused. hostname is the bare host the SDK connects to — no port or scheme — compared without regard to case. With Mtls or MtlsKeyHash, a request to a non-https endpoint fails with insecure_transport before anything is sent, and one line under the Driver’s HAAPI_DRIVER_HTTP log tag names the refused URL. See Upgrade — Server Key Pinning for the full trust model, the rotation playbook, and how to compute the hash.

Signed JWT#

Build a JWT signed with an asymmetric or symmetric key and present it as client_assertion. The server is configured with the matching public key (asymmetric) or shared secret (symmetric) to verify the assertion.

// Asymmetric (recommended for production):
let asymConfig = try ClientAuthenticationMethodJWTAsymmetric(
    pemFilename: "rsa-private-key.pem",
    signatureAlgorithm: .rs256,
    bundle: Bundle.main
)

// Symmetric (HMAC):
let symConfig = ClientAuthenticationMethodJWTSymmetric(
    signatureAlgorithm: .hs256,
    secretKey: "shared-secret-key"
)
// Asymmetric:
val asym = ClientAuthenticationMethodConfiguration.SignedJwt.Asymmetric(
    clientKeyStore = myClientKeyStore,
    clientKeyStorePassword = "passphrase".toCharArray(),
    alias = "signing-key",
    algorithmIdentifier =
        ClientAuthenticationMethodConfiguration.SignedJwt.Asymmetric.AlgorithmIdentifier.ES256
)

// Symmetric:
val sym = ClientAuthenticationMethodConfiguration.SignedJwt.Symmetric(
    secretKey = "shared-secret-key",
    signatureAlgorithm =
        ClientAuthenticationMethodConfiguration.SignedJwt.Symmetric.SignatureAlgorithm.HS256
)

When configuring DCR fallback or setting useAttestation to false, the client authentication method must be set to one of the methods above — not the default None. Otherwise the flow fails before reaching the server.

Was this helpful?