Upgrade — Redirects Are Refused#

The SDK no longer follows an HTTP redirect of the requests it sends to the identity server, on iOS or on Android. A redirect — a 301, 302, 303, 307 or 308 response with a non-empty Location — is refused, whatever its target, and nothing is sent to it. Any other 3xx, and a 3xx without a Location, is handled like any other response.

What Changed#

SituationBeforeAfter
Redirect to another scheme or hostFollowed by the platform HTTP stack, with the request’s credentials (Android’s HttpURLConnection followed only same-scheme redirects)Refused. Nothing is sent to the target; the request fails with haapi_redirect_refused
Redirect on the same scheme and hostFollowed, but the server usually rejected the redirected requestRefused with haapi_redirect_refused
Any other 3xx (such as 300 or 304), or a 3xx without a non-empty LocationHandled as a response — except on Android, where a 300 with a Location was followedNot refused and not followed: handled like any other response

These requests carry credentials — an access token and DPoP proof, a client secret or assertion, a registration token — that were issued for one URL. Following a redirect would send them to a URL the integrator never configured, and a DPoP proof would not be valid there anyway. Refusing every redirect keeps the credentials on the configured endpoints. Both platforms refuse the same set of redirects, on every request they send. The policy is always on and has no opt-out.

Affected Surfaces#

  • Driver Layer, iOS — every HaapiClient request and every HaapiTokenManager request, attestation included; a refusal throws HaapiError.invalidURLResponse whose cause is the new HaapiRedirectRefusal
  • Driver Layer, Android — every HaapiTokenManager request, attestation included, whatever your HttpURLConnectionProvider configures, and every HAAPI request through an OkHttp client built with addHaapiInterceptor, which now follows no redirect at all; a refusal is an IdsvrHaapiException.Unrecoverable whose cause is the new HaapiRedirectRefusal — through OkHttp, the cause of the IOException the call fails with
  • SDK Layer, iOS — HAAPI flow requests (start, followLink, submitForm and automatic redirection), OAuthTokenManager token, refresh and revocation requests, and the Dynamic Client Registration fallback registration; the new HaapiErrorCode.haapiRedirectRefused identifies the refusal
  • SDK Layer, Android — the same requests: HAAPI flow requests, OAuthTokenManager token, refresh and revocation requests, and the attestation, token and Dynamic Client Registration registration requests made while an accessor is created; the new DriverErrorCodes.HAAPI_REDIRECT_REFUSED identifies the refusal, and a refused revocation is never reported as a success
  • UI Layer, iOS and Android — handled for you; a refused redirect ends the flow with the haapi_redirect_refused error, and an OAuthLifecycle token operation fails with it

Server and Reverse-Proxy Configuration#

A correctly configured deployment does not redirect the SDK’s requests at all: the Curity Identity Server expresses Hypermedia Authentication API navigation as representation actions, not as HTTP redirects. A redirect on this traffic comes from a reverse proxy, a load balancer or a misconfiguration.

Do not answer HAAPI, token, revocation or DCR requests with a redirect. Terminate path rewrites and TLS at the proxy so the SDK reaches the final URL directly:

  • Rewrite paths inside the proxy. If the identity server is mounted under a different path than the one the app is configured with, rewrite the path when forwarding the request instead of answering with a 301, 302, 303, 307 or 308.
  • Configure the SDK with the public https URLs. Terminate TLS at the proxy, and do not rely on an http-to-https redirect — by then the first request has already crossed the network in cleartext.
  • Use the host and port the app is given. A proxy that redirects to its internal host or port is refused; forward to the internal address instead.
  • Check the configured URLs. The base URL (baseURL on iOS, baseUri on Android), the token endpoint, the authorization endpoint, the revocation endpoint and the registration endpoint must be the URLs that answer directly, with no redirect in between.

Handling the Refusal#

A refusal reports the error code haapi_redirect_refused. Its cause carries the redirect’s status code:

if case .error(let error) = result,
   let idsvrError = error as? IdsvrError,
   idsvrError.error == HaapiErrorCode.haapiRedirectRefused.rawValue
{
    let refusal = idsvrError.cause as? HaapiRedirectRefusal
    // refusal?.statusCode is the 3xx status
}

Its recovery suggestion is .unrecoverable(action: .introspectCause). Do not retry: the server answers the same way every time. Treat it as a server or reverse-proxy configuration problem and use the guidance above.

The error’s message and the log name no URL, because they may reach an end user; the attached response keeps the status code and the request URL’s scheme, host, port and path, but not the redirect’s headers or the query.

A refusal is an IdsvrHaapiException.Unrecoverable whose error is haapi_redirect_refused. Its cause carries the redirect’s status code:

try {
    val response = haapiManager.submitForm(form, parameters)
} catch (e: IdsvrHaapiException) {
    if (e.error == DriverErrorCodes.HAAPI_REDIRECT_REFUSED) {
        val refusal = e.cause as? HaapiRedirectRefusal
        // refusal?.statusCode is the 3xx status
    }
}

Its action is UnrecoverableAction.IntrospectCause. Do not retry: the server answers the same way every time. Treat it as a server or reverse-proxy configuration problem and use the guidance above.

Through an OkHttp client built with addHaapiInterceptor, a HAAPI request fails with an IOException whose cause is that exception:

val refusal = (ioException.cause as? IdsvrHaapiException)?.cause as? HaapiRedirectRefusal

The exception and the log name no URL, because they may reach an end user.

Whatever your HttpURLConnectionProvider configures, the SDK turns redirect following off on every connection. A connection that follows a redirect anyway — a wrapping connection that does not pass the setting on, for example — is detected only when it reports the URL it reached through getURL(). The request has then already reached the redirect target, and statusCode is the status of the response the redirect led to.

Check this before upgrading if you use addHaapiInterceptor on Android. It now turns redirect following off for the whole OkHttp client — followRedirects(false) and followSslRedirects(false) on the builder — so a request through that client that is not a HAAPI request receives the 3xx response itself. Use a separate client for traffic that should follow redirects. Do not turn redirect following back on for the HAAPI client: a redirected HAAPI request still fails with haapi_redirect_refused, but only after the hop has happened, so the request, with its access token, DPoP proof and session identifier, has already reached the redirect target.

Check this before upgrading if you supply your own URLSession delegate on iOS. The SDK handles redirects for its own requests itself. Your session delegate still receives authentication challenges — custom certificate trust keeps working — but its urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:) is no longer called for SDK requests.

Was this helpful?