Microsoft Entra External Authentication Method (EAM) provider
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 theCN=[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, sayentra_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
andexp
claims, given some leeway. -
Ensure the JWT
iss
matches the expected. -
Ensure the JWT
aud
matches the EAMclient_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 asentra_iss
parameter. -
The
tid
claim asentra_tid
parameter. -
The
oid
claim asentra_oid
parameter. -
The
preferred_username
claim asentra_preferred_username
parameter. -
The
sub
claim asentra_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 theopenid
scope value. -
The
long_lived
flag must be set tofalse
(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
}