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

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 RSA JSON Web Key (JWK) used to sign the ID tokens issued to it to include an X.509 certificate for the key in the JWK x5c parameter.

Locate the signing RSA key in the JWK set that you have configured for your Connect2id server. This is key that has a key type RSA and use signature (sig).

Example signing RSA key in JWK format:

{
  "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"
}

Create a self-signed X.509 certificate for the signing RSA key:

  • 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 you use the CN=[idp-domain-name] format.

  • The certificate "not-before" and "not-after" times should reflect the intended validity period for the signing key.

You can create the certificate and add it to the JWK with help of the open source OAuth 2.0 / OpenID Connect SDK and the Nimbus JOSE+JWT library by Connect2id.

Check out the following classes:

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 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(); // assumes now
Date notAfter = new Date(notBefore.getTime() + 365 * 24 * 60 * 60 * 1000L);

// Create a self-signed certificate
X509Certificate x509Cert = X509CertificateUtils.generate(
    new Issuer("idp.c2id.com"),
    new Subject("idp.c2id.com"),
    notBefore,
    notAfter,
    rsaJWK.toPublicKey(),
    rsaJWK.toRSAPrivateKey()
);

System.out.println(x509Cert.getIssuerX500Principal());
System.out.println(x509Cert.getSubjectX500Principal());
System.out.println(x509Cert.getNotBefore());
System.out.println(x509Cert.getNotAfter());
// Example output:
// CN=idp.c2id.com
// CN=idp.c2id.com
// Mon May 27 12:28:39 CET 2024
// Tue May 27 12:28:39 CET 2025

// Add the certificate to the RSA JWK
rsaJWK = new RSAKey.Builder(rsaJWK)
    .x509CertChain(Collections.singletonList(Base64.encode(x509Cert.getEncoded())))
    .build();

System.out.println(rsaJWK);

The RSA JWK with the X.509 certificate included in the x5c (X.509 certificate chain) parameter:

{
  "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",
  "x5c" : [ "MIICrzCCAZegAwIBAgIJAJlxVtDLjVecMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNVBAMTDGlkcC5jMmlkLmNvbTAeFw0yNDA1MjcwOTMxNTVaFw0yNTA1MjcwOTMxNTVaMBcxFTATBgNVBAMTDGlkcC5jMmlkLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIa8A/pXPiMM8InQgDZsuKrImZNdcqfZB8pDrj9Mjp8It+laJmE0FRM2V4IdrAyaRoALrXULnYMC6FfLVUrExvUkB4CYx/T3f/VeAG+jwDcmTRug0noqtUH4hZRD+jm7dCW0Qd1u19r5MTlWjaFq2LJGaHcVNldKIHlS/y5HAhgMjbto3cd49A4yWrTVZO8rLTcW/UXx4SRoKYiJDuUt0IkiFYWwSmiR3GRzxdb6vYNCTBrlAiPjk0lYOlwZEyxBsNQv0e91g/6FRAX3V215y7pKCxwUtdadzsVO3+K38QP3/+9v1MJlhiZ7Kkt4moW5rNxZL6qC/cf1Y9xobiUn/e0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZK+ZpEaBpfwBlEvSUU9kOnZdU9LxgYSRbnp+T5DO7yJxuUq3PrmFCwip176yqk6ubfFdXiDb0WLhiifFOxtg8ZWPeyPgNnXGmK5L3rFdkaTgI4PQhAb1zeT7betNCHFz9P26sQRO4quInAEultU5BBRyF55q/JEmHdxGhYGA0gR2GOQzd3qpc1qL427nVLYuZJ6eS+OOoduhnpzTbH1KrlXTDDlB4D7yCj8p/fdZftZ8EgrX2z0/MRieeDmz0mN+eIgW/A/0v3AYoSaGiCvk6xjNLAO7KW92Lp/nsWyZjzxSyMiBZ9E8l1QJuiDKgaKvPRLmr9qkDG/EXkgOcTiGrA==" ]
}

Update the signing JWK in the JWK set of the Connect2id server. You can verify the published JWK set by checking the /jwks.json resource of the Connect2id server.

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 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 submission of a consent to the Connect2id server:

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