Skip to content
Connect2id

How to become an External Authentication Method (EAM) provider for Microsoft Entra


This guide is currently being updated.


Microsoft Entra ID supports multi-factor authentication (MFA) of end-users. In May 2024 Microsoft announced that authentication methods, such as biometric, risk-based or smart-card based authentication methods provided by 3rd parties can be integrated into Entra ID by means of a special OpenID Connect profile devised by Microsoft.

This guide explains the necessary Connect2id server configuration and customisation to become an External Authentication Method (EAM) provider for Entra ID. It is based on the following Microsoft reference version from May 2024:

https://learn.microsoft.com/en-us/entra/…external-method-provider

1. The flow

Entra ID invokes the EAM provider with an OpenID authentication request that uses the implicit flow to obtain an ID token (response_type=id_token) that represents the performed authentication method.

The flow makes bespoke use of the id_token_hint request parameter, to convey signed information and context about the end-user that is to be authenticated.

2. Connect2id server configuration

op.authz.advertisedScopes

Make sure the op.authz.advertisedScopes configuration property includes the openid value.

op.authz.advertisedScopes=openid

The configuration property may include other scope values. They are not required by Entra ID and will not affect its operation.

op.authz.responseTypes

Make sure the op.authz.responseTypes configuration property includes the id_token value.

op.authz.responseTypes=id_token

The configuration property may include other response type values. They are not required by Entra ID and will not affect its operation.

op.authz.responseModes

Make sure the op.authz.responseModes configuration property includes the form_post value.

op.authz.responseModes=form_post

The configuration property may include other response mode values. They are not required by Entra ID and will not affect its operation.

op.authz.requestParamsInAuthPrompt

Make sure the op.authz.requestParamsInAuthPrompt configuration property includes the following values. They will cause JWT claims extracted from the validated Entra ID id_token_hint to appear in the authentication prompt to the login page, to be used as Entra ID specific inputs to the end-user authentication.

op.authz.requestParamsInAuthPrompt=entra_iss,entra_tid,entra_oid,entra_preferred_username,entra_sub,client-request-id

The configuration property may include other parameter name values. They are not required by Entra ID and will not affect its operation.

op.idToken.jwsAlgs

Make sure the op.idToken.jwsAlgs configuration property includes the RS256 value.

op.idToken.jwsAlgs=RS256

The configuration property may include other JWS algorithm names. They are not required by Entra ID and will not affect its operation.

3. Connect2id server JWK set

Entra ID expects the published RSA JSON Web Key (JWK) instances for the ID token validation to have these particular parameters:

  • kty – a key type RSA.
  • use – a key use sig (signature).
  • x5c – contain a single self-signed X.509 certificate for the key.
  • x5t – the SHA-1 thumbprint of the X.509 certificate.
  • kid – the key ID is set to the x5t value.
  • n – the modulus of the public RSA key, with accepted sizes 2048, 3072 or 4096 bits.
  • e – the exponent of the public RSA key.

Further, the public server JWK set must only include signing RSA keys. Other key types that the Connect2id server supports, such as EC and EdDSA, must not be present in the public JWK set.

Procedure:

  1. Get the configured static or legacy JWK set for the Connect2id server.

  2. Remove all keys with a kty of EC.

  3. Remove all keys with a kty of OKP.

  4. Remove all keys with a kty of RSA and use enc.

  5. For each signing RSA key (there may be multiple if keys are rotated), that is each key with a kty of RSA and use sig:

    1. Create a self-signed X.509 certificate for the key and store it in the x5c parameter:

      • The certificate issuer and subject DNs must have the same value, for example CN=idp.c2id.com. The DN in this example indicates the domain name of the OpenID provider. Entra ID does not appear to require a particular DN attribute format for the issuer and subject DNs, but it is suggested to use the CN=[idp-domain-name] format.

      • The certificate “not-before” and “not-after” times must reflect the intended validity period for the signing key.

    2. Compute the SHA-1 thumbprint of the certificate and store it in the x5t parameter.

    3. Modify the kid so that it takes the value of the x5t parameter.

  6. Update the configured JWK set for the Connect2id server.

  7. Verify the published JWK set by checking the /jwks.json resource of the Connect2id server.

Example regular signing RSA JWK:

{
  "kty" : "RSA",
  "use" : "sig",
  "kid" : "CXup",
  "n"   : "hrwD-lc-IwzwidCANmy4qsiZk11yp9kHykOuP0yOnwi36VomYTQVEzZXgh2sDJpGgAutdQudgwLoV8tVSsTG9SQHgJjH9Pd_9V4Ab6PANyZNG6DSeiq1QfiFlEP6Obt0JbRB3W7X2vkxOVaNoWrYskZodxU2V0ogeVL_LkcCGAyNu2jdx3j0DjJatNVk7ystNxb9RfHhJGgpiIkO5S3QiSIVhbBKaJHcZHPF1vq9g0JMGuUCI-OTSVg6XBkTLEGw1C_R73WD_oVEBfdXbXnLukoLHBS11p3OxU7f4rfxA_f_72_UwmWGJnsqS3iahbms3FkvqoL9x_Vj3GhuJSf97Q",
  "e"   : "AQAB",
  "d"   : "bmpuqB4PIhJcndRs_i0jOXKjyQzwBXXq2GuWxPEsgFBYx7fFdCuGifQiytMeSEW2OQFY6W7XaqJbXneYMmoI0qTwMQcD91FNX_vlR5he0dNlpZqqYsvVN3c_oT4ENoPUr4GF6L4Jz74gBOlVsE8rvw3MVqrfmbF543ONBJPUt3d1TjKwaZQlgPji-ycGg_P7K-dKxpyfQsC8xMmVmiAF4QQtnUa9vMgiChiO8-6VzGm2yWWyIUVRLxSohrbSNFhqF2zeWXePAw0_nzeZh3IDIMS5ABo92Pry4N3X-X7v_7nf8MGngK4duQ_1UkkLk-3u0I3tk_glsarDN0tYhzPwAQ"
}

The RSA JWK above, modified to include the x5c (X.509 certificate chain) and x5t (X.509 certificate SHA-1 thumbprint) parameters, and the kid updated to match the x5t value:

{
  "kty" : "RSA",
  "use" : "sig",
  "kid" : "jU7oJWH7PDNDM8qyKCAV5WLjmKs",
  "n"   : "hrwD-lc-IwzwidCANmy4qsiZk11yp9kHykOuP0yOnwi36VomYTQVEzZXgh2sDJpGgAutdQudgwLoV8tVSsTG9SQHgJjH9Pd_9V4Ab6PANyZNG6DSeiq1QfiFlEP6Obt0JbRB3W7X2vkxOVaNoWrYskZodxU2V0ogeVL_LkcCGAyNu2jdx3j0DjJatNVk7ystNxb9RfHhJGgpiIkO5S3QiSIVhbBKaJHcZHPF1vq9g0JMGuUCI-OTSVg6XBkTLEGw1C_R73WD_oVEBfdXbXnLukoLHBS11p3OxU7f4rfxA_f_72_UwmWGJnsqS3iahbms3FkvqoL9x_Vj3GhuJSf97Q",
  "e"   : "AQAB",
  "d"   : "bmpuqB4PIhJcndRs_i0jOXKjyQzwBXXq2GuWxPEsgFBYx7fFdCuGifQiytMeSEW2OQFY6W7XaqJbXneYMmoI0qTwMQcD91FNX_vlR5he0dNlpZqqYsvVN3c_oT4ENoPUr4GF6L4Jz74gBOlVsE8rvw3MVqrfmbF543ONBJPUt3d1TjKwaZQlgPji-ycGg_P7K-dKxpyfQsC8xMmVmiAF4QQtnUa9vMgiChiO8-6VzGm2yWWyIUVRLxSohrbSNFhqF2zeWXePAw0_nzeZh3IDIMS5ABo92Pry4N3X-X7v_7nf8MGngK4duQ_1UkkLk-3u0I3tk_glsarDN0tYhzPwAQ",
  "x5c" : [ "MIICrzCCAZegAwIBAgIJALwnrEETkIVMMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNVBAMTDGlkcC5jMmlkLmNvbTAeFw0yNjA0MDMwNjM1NDNaFw0yNzA0MDMwNjM1NDNaMBcxFTATBgNVBAMTDGlkcC5jMmlkLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIa8A/pXPiMM8InQgDZsuKrImZNdcqfZB8pDrj9Mjp8It+laJmE0FRM2V4IdrAyaRoALrXULnYMC6FfLVUrExvUkB4CYx/T3f/VeAG+jwDcmTRug0noqtUH4hZRD+jm7dCW0Qd1u19r5MTlWjaFq2LJGaHcVNldKIHlS/y5HAhgMjbto3cd49A4yWrTVZO8rLTcW/UXx4SRoKYiJDuUt0IkiFYWwSmiR3GRzxdb6vYNCTBrlAiPjk0lYOlwZEyxBsNQv0e91g/6FRAX3V215y7pKCxwUtdadzsVO3+K38QP3/+9v1MJlhiZ7Kkt4moW5rNxZL6qC/cf1Y9xobiUn/e0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEADvdpxB6zphjEXtA8x0IoajFbv6QHb6lGwSBK7QDJUuw5v1VjxIMCV3ysjZNaycLAQ5Z3xFY59EhvEWuSF4IoTpTucTnkCgB9tXWWc3rtn/150mpRwG7sv37A6bl2MdmL96HDT/wuq8WFygrXpvndbGPSibaAgtc1pkg6xqgE5JulKK8SrPne1qAaBX9nFrAtRgYfZ+bRigkmiHt1V6AbgxzpxBOZp9MlP2ju725R68gqdCdWfBslQkhwYbXcljchxh8n5qKv11+VS7Zq0CLaTeekiWunJfSj5/wnVTtLzow8K27IjfBObVJ9Xym5hc+vtkyidAfYre0BBBjhmVDuhA==" ],
  "x5t" : "jU7oJWH7PDNDM8qyKCAV5WLjmKs"
}

The RSA JWK modification can be performed with help of the Connect2id open source OAuth 2.0 / OpenID Connect SDK (v11.37+), the Nimbus JOSE+JWT library (v10.9+) and the BouncyCastle cryptography libraries for Java.

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>10.9</version>
</dependency>
<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>oauth2-oidc-sdk</artifactId>
    <version>11.37</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.81</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.81</version>
</dependency>

The necessary classes for the operations:

Example Java code:

import java.security.cert.*;
import java.util.*;
import com.nimbusds.jose.util.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.oauth2.sdk.id.*;
import com.nimbusds.oauth2.sdk.util.*;

// Parse the signing RSA JWK
String jwkString =
        "{" +
        "  \"kty\" : \"RSA\"," +
        "  \"use\" : \"sig\"," +
        "  \"kid\" : \"CXup\"," +
        "  \"n\"   : \"hrwD-lc-IwzwidCANmy4qsiZk11yp9kHykOuP0yOnwi36VomYTQVEzZXgh2sDJpGgAutdQudgwLoV8tVSsTG9SQHgJjH9Pd_9V4Ab6PANyZNG6DSeiq1QfiFlEP6Obt0JbRB3W7X2vkxOVaNoWrYskZodxU2V0ogeVL_LkcCGAyNu2jdx3j0DjJatNVk7ystNxb9RfHhJGgpiIkO5S3QiSIVhbBKaJHcZHPF1vq9g0JMGuUCI-OTSVg6XBkTLEGw1C_R73WD_oVEBfdXbXnLukoLHBS11p3OxU7f4rfxA_f_72_UwmWGJnsqS3iahbms3FkvqoL9x_Vj3GhuJSf97Q\"," +
        "  \"e\"   : \"AQAB\"," +
        "  \"d\"   : \"bmpuqB4PIhJcndRs_i0jOXKjyQzwBXXq2GuWxPEsgFBYx7fFdCuGifQiytMeSEW2OQFY6W7XaqJbXneYMmoI0qTwMQcD91FNX_vlR5he0dNlpZqqYsvVN3c_oT4ENoPUr4GF6L4Jz74gBOlVsE8rvw3MVqrfmbF543ONBJPUt3d1TjKwaZQlgPji-ycGg_P7K-dKxpyfQsC8xMmVmiAF4QQtnUa9vMgiChiO8-6VzGm2yWWyIUVRLxSohrbSNFhqF2zeWXePAw0_nzeZh3IDIMS5ABo92Pry4N3X-X7v_7nf8MGngK4duQ_1UkkLk-3u0I3tk_glsarDN0tYhzPwAQ\"" +
        "}";

RSAKey rsaJWK = RSAKey.parse(jwkString);

// Set the validity time window of the certificate
Date notBefore = new Date(); // now
Date notAfter = new Date(notBefore.getTime() + 365 * 24 * 60 * 60 * 1000L);

X509Certificate x509Cert = X509CertificateUtils.generateSelfSigned(
        new Issuer("idp.c2id.com"),
        notBefore,
        notAfter,
        rsaJWK.toPublicKey(),
        rsaJWK.toPrivateKey());

System.out.println("Issuer: " + x509Cert.getIssuerX500Principal());
System.out.println("Subject: " + x509Cert.getSubjectX500Principal());
System.out.println("Not before: " + x509Cert.getNotBefore());
System.out.println("Not after: " + x509Cert.getNotAfter());
// Example output:
// Issuer: CN=idp.c2id.com
// Subject: CN=idp.c2id.com
// Not before: Fri Apr 03 09:35:43 EEST 2026
// Not after: Sat Apr 03 09:35:43 EEST 2027

// Compute the SHA-1 thumbprint
Base64URL x5t = X509CertUtils.computeSHA1Thumbprint(x509Cert);

System.out.println("x5t: " + x5t);
// Example output:
// x5t: jU7oJWH7PDNDM8qyKCAV5WLjmKs

// Add the certificate and the thumbprint to the RSA JWK, 
// update the key ID
rsaJWK = new RSAKey.Builder(rsaJWK)
        .x509CertChain(Collections.singletonList(Base64.encode(x509Cert.getEncoded())))
        .x509CertThumbprint(x5t)
        .keyID(x5t.toString())
        .build();

System.out.println(rsaJWK);

4. Client registration

Entra ID must be registered as a client with the Connect2id in order for the server to accept OpenID authentication requests from it.

The client must be registered with the ID provisioned by Entra ID, using the preferred_client_id parameter. The redirect_uris must include the URL where Entra ID is expecting the issued ID token to be posted. The response_types must be include the id_token value.

Example client registration request:

POST /clients HTTP/1.1
Host: idp.c2id.com
Content-Type: application/json
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

{
  "preferred_client_id" : "56c2b5e3-c41d-4c14-abcf-72d407f57650",
  "grant_types"         : [ "implicit" ],
  "response_types"      : [ "id_token" ],
  "redirect_uris"       : [ "https://login.microsoftonline.com/common/federation/externalauthprovider" ]
}

Example registration response:

HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
  "client_id"                    : "56c2b5e3-c41d-4c14-abcf-72d407f57650",
  "client_id_issued_at"          : 1716880854,
  "registration_client_uri"      : "https://idp.c2id.com/clients/56c2b5e3-c41d-4c14-abcf-72d407f57650",
  "registration_access_token"    : "k3hQH_fMokzYde1uq6NKtJLeYZDVCWjvwwpYgKM8HBI.YSxpLHIscCxjLGoscyx0LHNjcCxzdXJuLGlkLHNlYyxk",
  "application_type"             : "web",
  "subject_type"                 : "public",
  "grant_types"                  : [ "implicit" ],
  "response_types"               : [ "id_token" ],
  "redirect_uris"                : [ "https://login.microsoftonline.com/common/federation/externalauthprovider" ],
  "token_endpoint_auth_method"   : "none",
  "id_token_signed_response_alg" : "RS256"
}

5. Login page

HTTP POST support

OpenID authentication requests and responses to / from the EAM provider are likely to include large payloads that include signed content (the id_token_hint and the id_token). Entra ID therefore utilises the safer HTTP POST method.

The login page that the EAM provider deploys for the Connect2id server must support the HTTP POST method for receiving authorisation requests.

It must also support the form_post response mode, as specified here. The login page must trigger a form_post response when the Connect2id server completes the authorisation session with final response - form_post.

Change the id_token_hint parameter name

Entra ID utilises the standard id_token_hint request parameter to pass important user and context information to the EAM provider, however, its JWT payload and signer do not comply with the standard and will cause the Connect2id server to discard the authorisation request as invalid.

The login page must therefore, upon receiving an HTTP POST request from a client, check if this is a request from Entra ID and if so, change the id_token_hint query string parameter name to another, so that the parameter doesn’t get processed in the standard fashion.

To detect if the request is from Entra ID:

  • Check if the client_id query string parameter is the one for Entra ID as an OAuth 2.0 client / OpenID relying party.

  • Check for the presence of the custom client-request-id that Entra ID sets for logging and troubleshooting purposes.

  • Both of the above methods.

If an Entra ID request is detected:

  • Replace the name of the id_token_hint query string parameter with another name that isn’t used in standard requests, say entra_id_token_hint.

  • Add the standard OpenID Connect prompt=login query string parameter, to ensure the request will always cause the Connect2id server to ask for end-user authentication, even if the current user session has matching ACR level. If you Connect2id server deployment is configured with op.authz.alwaysPromptForAuth the addition of this extra request parameter can be skipped.

With the rewritten query string initiate an authorisation session with the Connect2id server as usual.

6. Validate the Entra ID id_token_hint

The id_token_hint received from Entra ID must be validated, as specified in the EAM documentation.

We recommend creating a small plugin for this task, using the Connect2id server AuthorizationRequestValidator SPI.

The plugin must perform the following on detecting an entra_id_token_hint parameter in the OpenID authentication request (using the rewritten query string parameter name above):

  • Parse the entra_id_token_hint as a signed JWT.

  • Validate the JWT signature using the public RSA key made available by Microsoft for Entra ID.

  • Ensure the current system time is within the time time window defined by the JWT iat and exp claims, given some leeway.

  • Ensure the JWT iss matches the expected.

  • Ensure the JWT aud matches the EAM client_id with Entra ID.

If the JWT is found to be invalid, log the reason together with the custom client-request-id request parameter and return an OAuth 2.0 invalid_request error.

If the JWT is valid, extract its claims. The following claims must then be added as custom parameters to the AuthorizationRequest that the plugin will return to the Connect2id server to continue the flow:

  • The iss claim as entra_iss parameter.

  • The tid claim as entra_tid parameter.

  • The oid claim as entra_oid parameter.

  • The preferred_username claim as entra_preferred_username parameter.

  • The sub claim as entra_sub parameter.

A SampleEntraIDTokenHintValidator implementing the AuthorizationRequestValidator SPI can be found in the Connect2id server SDK test suite. It validates the id_token_hint with help of the Nimbus JOSE+JWT library. Feel free to reuse the sample plugin code or modify it as necessary.

7. During the authorisation session

Authentication step

Requests from Entra ID to the Connect2id server as an EAM provider will trigger the server to bring up an authentication prompt that includes the following parameters, as extracted by the id_token_hint validator plugin:

  • entra_iss
  • entra_tid
  • entra_oid
  • entra_preferred_username
  • entra_sub

Together with the acr parameter, they can be used as inputs to the process to authenticate the end-user at the requested level and with the factor(s) that are appropriate to it.

When the end-user is authenticated, the subject session that gets submitted to the Connect2id server must have the acr and amr set appropriately. The sub must match the entra_sub from the Entra ID id_token_hint. If for some reason the end-users at the IdP have different local IDs that the Entra ID subject IDs, additional session management may be needed.

Example submission of a subject authentication to the Connect2id server, the acr claim will be copied into the issued ID token:

{
  "sub" : "mBfcvuhSHkDWVgV72x2ruIYdSsPSvcj2R0qfc6mGEAA",
  "acr" : "possessionorinherence",
  "amr" : [ "fido", "face" ]
}

If the end-user was not authenticated, the authorisation session must be closed with an access_denied OAuth 2.0 error.

Consent step

When submitting the end-user’s consent to the Connect2id server:

  • The consented scope must include only the openid scope value.

  • The long_lived flag must be set to false (consent not persisted).

  • All other consent parameters can be left at their default settings.

Example consent:

{
  "scope"      : [ "openid" ],
  "long_lived" : false
}