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#
| Situation | Before | After |
|---|---|---|
| Redirect to another scheme or host | Followed 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 host | Followed, but the server usually rejected the redirected request | Refused with haapi_redirect_refused |
Any other 3xx (such as 300 or 304), or a 3xx without a non-empty Location | Handled as a response — except on Android, where a 300 with a Location was followed | Not 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
HaapiClientrequest and everyHaapiTokenManagerrequest, attestation included; a refusal throwsHaapiError.invalidURLResponsewhosecauseis the newHaapiRedirectRefusal - Driver Layer, Android — every
HaapiTokenManagerrequest, attestation included, whatever yourHttpURLConnectionProviderconfigures, and every HAAPI request through an OkHttp client built withaddHaapiInterceptor, which now follows no redirect at all; a refusal is anIdsvrHaapiException.Unrecoverablewhosecauseis the newHaapiRedirectRefusal— through OkHttp, thecauseof theIOExceptionthe call fails with - SDK Layer, iOS — HAAPI flow requests (
start,followLink,submitFormand automatic redirection),OAuthTokenManagertoken, refresh and revocation requests, and the Dynamic Client Registration fallback registration; the newHaapiErrorCode.haapiRedirectRefusedidentifies the refusal - SDK Layer, Android — the same requests: HAAPI flow requests,
OAuthTokenManagertoken, refresh and revocation requests, and the attestation, token and Dynamic Client Registration registration requests made while an accessor is created; the newDriverErrorCodes.HAAPI_REDIRECT_REFUSEDidentifies 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_refusederror, and anOAuthLifecycletoken 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,307or308. - Configure the SDK with the public
httpsURLs. Terminate TLS at the proxy, and do not rely on anhttp-to-httpsredirect — 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 (
baseURLon iOS,baseUrion 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? HaapiRedirectRefusalThe 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.