Skip to main content
Developer

3DS app2app ACS integration#

View as Markdown
On this page

This page describes how a 3-D Secure Access Control Server (ACS) uses the 3ds_info parameter on /app-authorize to offer an app2app app-switch instead of falling back to a browser challenge.

This is intended as a supplement to the App2App flow already described in App2App.

Audience#

This page is for the card issuer's ACS, not the merchant's app. The ACS is the caller because it is the party that receives and decrypts the EMV 3-D Secure Device Information from the AReq message during the cardholder authentication request. The merchant app never sees this data and cannot call this endpoint.

Requirements#

Scopes#

You will need a BankID client with the following scopes enabled:

  • permissions/client
  • permissions/app2app
  • permissions/ciba

Include all three scopes in the access token used to create the mixed-intent permission. The token used for the required /bc-authorize fallback must also include permissions/ciba.

Intents#

You must create a permission with the following intents: intents: ["app2app", "ciba"]

/bc-authorize fallback#

A 403 access_denied with error_description: "No matching device found" from /app-authorize means App2App has not started and is a normal, expected outcome. Some devices and platforms cannot use app2app, and the contract is designed around that. When it happens, fall back to the CIBA flow via /bc-authorize. This fallback is required, not optional: it is what lets game consoles, smart TVs, and any other device we cannot reach via app2app still complete authentication, by sending a push message to the BankID app on a usable device instead of attempting an app-switch.

Reuse the same still-valid, unconsumed permission, send its permissionToken as login_hint_token and its saved bindingMessage as binding_message in the /bc-authorize request, then poll the auth_req_id returned by CIBA.

Use 3ds_info instead of device_info#

The rest of this page describes how to use 3ds_info instead of device_info.

The 3ds_info allowlist#

The server accepts a flat JSON object of EMVCo Device Information identifiers, encoded as base64url and sent as the 3ds_info form value. Only the identifiers below are accepted; any other key is dropped silently before the request is processed further.

Use this allowlist when selecting keys from the SDK payload. Retain only keys in this set and omit everything else. Non-allowlisted fields may contain additional personal data and must not be sent. The server silently ignores non-allowlisted keys if they are received, but clients must filter them before building 3ds_info.

Java:

Set<String> allowedFields = Set.of(
    "C001", "D001", "C002", "D002", "C003", "D003", "C004", "C008", "D008",
    "A050", "A044", "C005", "D005", "D027", "C006", "D006", "C013", "D013",
    "C016", "DV", "C010", "D029"
);

C#:

var allowedFields = new HashSet<string>(StringComparer.Ordinal)
{
    "C001", "D001", "C002", "D002", "C003", "D003", "C004", "C008", "D008",
    "A050", "A044", "C005", "D005", "D027", "C006", "D006", "C013", "D013",
    "C016", "DV", "C010", "D029",
};

Send every available allowlisted field your 3DS SDK exposes. Omit an unavailable allowlisted field by leaving the key out. Never send an empty string or null as a placeholder. See pitfalls below.

Tier 1 — device identity#

These describe the device itself and carry the most weight when we decide whether an app-switch can be offered. Send every available allowlisted field your SDK provides.

IdentifierDescriptionTypical source in the 3DS SDK
C001Cardholder device operating system name (for example Android, iOS).Reported directly by the 3DS SDK as the device operating system name.
D001Same as C001, used by SDKs that report Device Information as a duplicated C/D pair.Reported alongside C001 on split-SDK integrations; used as a fallback when C001 is absent.
C002Device model identifier, for example a marketing name or internal model code.Reported by the SDK as the device model or marketing name.
D002Same as C002, duplicated C/D pair.Reported alongside C002 on split-SDK integrations.
C003Operating system name and version string.Reported by the SDK as the OS version string.
D003Same as C003, duplicated C/D pair.Reported alongside C003 on split-SDK integrations.
C004Device locale identifier.Reported by the SDK from the device's configured locale.
C008Device screen resolution.Reported by the SDK as the screen resolution string.
D008Same as C008, duplicated C/D pair.Reported alongside C008 on split-SDK integrations.

Tier 2 — manufacturer repair#

IdentifierDescriptionTypical source in the 3DS SDK
A050Device manufacturer name.Reported by the SDK from platform device metadata.
A044Device brand name, which can differ from the manufacturer on some Android OEM builds.Reported by the SDK from platform device metadata.

Tier 3 — context#

IdentifierDescriptionTypical source in the 3DS SDK
C005Device timezone offset or identifier.Reported by the SDK from the device's configured timezone.
D005Same as C005, duplicated C/D pair.Reported alongside C005 on split-SDK integrations.
D027Human-readable timezone name (for example a city list).Reported by the SDK where the platform exposes a display timezone name.
C006Device timezone offset, expressed in minutes from UTC.Reported by the SDK from the device's configured timezone offset.
D006Same as C006, duplicated C/D pair.Reported alongside C006 on split-SDK integrations.

Tier 4 — provenance#

IdentifierDescriptionTypical source in the 3DS SDK
C0133DS SDK application identifier.Assigned by the ACS or DS to the 3DS Requestor App and reported back by the SDK.
D013Same as C013, duplicated C/D pair.Reported alongside C013 on split-SDK integrations.
C0163DS SDK reference number.Provided by the SDK vendor and reported by the SDK.
DV3DS Device Information version number.Reported by the SDK as the version of its Device Information payload format.

Tier 5 — local network#

IdentifierDescriptionTypical source in the 3DS SDK
C010IP address of the cardholder device on its local network.Reported by the SDK from the device's local network interface.
D029Same as C010, duplicated C/D pair.Reported alongside C010 on split-SDK integrations.

Worked example#

Decoded 3ds_info JSON object:

{
  "C001": "Android",
  "C002": "SM-S908B",
  "C003": "Android BAKLAVA 16 API 36",
  "C006": "-120",
  "C008": "1080x2340",
  "A044": "samsung"
}

The same value, base64url-encoded:

eyJDMDAxIjoiQW5kcm9pZCIsIkMwMDIiOiJTTS1TOTA4QiIsIkMwMDMiOiJBbmRyb2lkIEJBS0xBVkEgMTYgQVBJIDM2IiwiQzAwNiI6Ii0xMjAiLCJDMDA4IjoiMTA4MHgyMzQwIiwiQTA0NCI6InNhbXN1bmcifQ

Full form POST to /app-authorize:

POST /app-authorize HTTP/1.1
Content-Type: application/x-www-form-urlencoded

login_hint_token=<login_hint_token>&scope=openid&3ds_info=eyJDMDAxIjoiQW5kcm9pZCIsIkMwMDIiOiJTTS1TOTA4QiIsIkMwMDMiOiJBbmRyb2lkIEJBS0xBVkEgMTYgQVBJIDM2IiwiQzAwNiI6Ii0xMjAiLCJDMDA4IjoiMTA4MHgyMzQwIiwiQTA0NCI6InNhbXN1bmcifQ

A registered device that can be reached via app2app yields a 200 with an app-switch link:

{
  "authorize_uri": "https://app.bankid.no/authorize?..."
}

A device that does not match, or is not currently reachable via app2app, yields a 403:

{
  "error": "access_denied",
  "error_description": "No matching device found"
}

Treat that 403 as the expected signal to fall back to /bc-authorize, not as an integration bug.

400 versus 403#

ConditionResult
3ds_info is not valid base64url, or does not decode to valid JSON400 invalid_request
Both device_info and 3ds_info are present, or neither is present400 invalid_request
Unknown or non-allowlisted keys are presentDropped silently, request proceeds
A value is an unexpected shape or over-longDropped silently, request proceeds
The device information does not match a device registered for app2app403 access_denied
The login hint does not identify a user enrolled for Substantial authentication400 unknown_user_id

A well-formed 3ds_info request returns 400 unknown_user_id when the login hint does not identify a user enrolled for Substantial authentication. Treat this as a terminal outcome. Do not fall back to /bc-authorize; that fallback is for 403 access_denied with error_description: "No matching device found", which means app-switch is unavailable for the identified user from this device.

Pitfalls#

  • Pass values through verbatim. Do not normalise casing, trim whitespace, or reformat any value before sending it. Normalising discards information we cannot recover.
  • Send every available allowlisted field your SDK exposes. Omit unavailable allowlisted fields by leaving their keys out of the object entirely. Never send an empty string or null as a placeholder for a missing value.
  • Send a flat object. 3ds_info is the flat set of allowlisted key/value pairs, not the full DD/DPNA/SW envelope some 3DS SDKs produce internally.
  • An unrecognised key will not fail your request. A key outside the allowlist is dropped rather than rejected, so a new field from an SDK upgrade cannot break your integration. That is a robustness guarantee, not permission to send everything: build 3ds_info from the allowlist. Non-allowlisted fields may contain additional personal data and must not be sent. Do not rely on us to discard data you should not have sent. A field you cannot populate is omitted entirely, never sent as an empty string or null.
  • Identifiers are uppercase and matched case-sensitively. c001 is not the same key as C001 and will be silently dropped as unrecognised. Send identifiers exactly as EMVCo defines them.
  • device_info and 3ds_info are mutually exclusive. Sending both on the same request is a 400, not a merge of the two.
  • A device-match 403 is a normal, expected outcome. The CIBA fallback described above is required rather than optional. Build it into your integration from the start instead of treating it as an edge case.

Wire versus contract#

LevelRule
WireEvery field is optional. A missing field is never a 400.
ContractSend every available allowlisted field your SDK exposes.

The parser is lenient so that a partial payload degrades gracefully in production. That leniency is not permission to send less than your SDK gives you. The contract is that you send the full set of allowlisted fields you have available, every time.

Not a fraud control#

This check exists to decide whether the app-switch can be offered at all. It is not a fraud control, and it does not replace one. Fraud detection and risk scoring remain the responsibility of the ACS engine.

Was this page helpful?