How to become an external Multifactor Authentication (MFA) 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 MFA provider for Entra ID. It is based on the following Microsoft reference version from February 2026:
https://learn.microsoft.com/en-us/entra/…external-method-provider
1. The flow
Entra ID invokes the MFA provider with an OpenID authentication
request that uses a profile of the
implicit flow to obtain an ID token (response_type=id_token) representing
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 requirements for Entra ID
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. In
practice this may be an arbitrary identifier. For maximum interoperability it
can be set to the
x5tvalue, although this is not strictly required. - 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 supported by the Connect2id server, such as EC and EdDSA, must not be present in the public JWK set used for Entra ID.
If the above key requirements are not met Entra will produce an error.
Generate JWK set
To meet the above requirements, generate the
initial Connect2id server JWK set as follows, where domain is the domain of
the configured issuer URL and days is the
validity period of the self-signed certificate:
java -jar jwks-gen.jar op \
-noEC -noEdDSA -noEnc \
-x5cSelfSigned \
-x5cSubject domain \
-x5cValidityDays days \
-x5t \
-b64 jwkSet.json.b64
The resulting BASE64URL-encoded JWK set should then be used as static key configuration for the Connect2id server.
Programmatic modification of an existing JWK set
Alternatively, an existing Connect2id server JWK set can be modified programmatically to fulfil the requirements. This approach is retained for backward compatibility and is not recommended for new deployments.
Procedure:
-
Remove all keys with:
kty=ECkty=OKPkty=RSAanduse=enc
-
For each signing RSA key (
kty=RSAanduse=sig):-
Create a self-signed X.509 certificate for the key and include it in
x5c:-
The issuer and subject DNs must be identical, for example
CN=idp.c2id.com. The DN typically reflects the OpenID provider domain. -
The certificate validity period (bounded by “not-before” and “not-after”) should match the intended lifetime of the signing key.
-
-
Compute the SHA-1 thumbprint of the certificate and include it as
x5t. -
Optionally set the
kidto thex5tvalue for consistency.
-
-
Update the configured JWK set.
-
Verify the published JWK set at the /jwks.json endpoint.
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:
- OAuth 2.0 / OpenID Connect SDK:
- Nimbus JOSE+JWT:
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 MFA 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 MFA 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 id_token_hint request parameter to pass important user and context information to the MFA provider, however, its JWT payload and signer do not comply with OpenID Connect 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 request 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_idmatches the registered Entra ID client. -
Check for the presence of the custom
client-request-idthat Entra ID sets for logging and troubleshooting purposes. -
Prefer using both checks for reliability.
If an Entra ID request is detected:
-
Replace the name of the
id_token_hintrequest parameter with another name that isn’t used in standard requests, sayentra_id_token_hint. -
Add the standard OpenID Connect
prompt=loginrequest parameter, to ensure the request will always cause the Connect2id server to ask for end-user authentication, even if the current user session has a matching ACR level. If the Connect2id server deployment is configured with op.authz.alwaysPromptForAuth the addition of this extra request parameter can be skipped.
With the rewritten request 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 MFA provider
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 request
parameter name above):
-
Parse the
entra_id_token_hintas 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 window defined by the JWT
iatandexpclaims, given some leeway. -
Ensure the JWT
issmatches the expected. -
Ensure the JWT
audmatches theclient_idfor the MFA provider.
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
issclaim asentra_issparameter. -
The
tidclaim asentra_tidparameter. -
The
oidclaim asentra_oidparameter. -
The
preferred_usernameclaim asentra_preferred_usernameparameter. -
The
subclaim asentra_subparameter.
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 MFA 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_issentra_tidentra_oidentra_preferred_usernameentra_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 than 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
ended with an
access_denied OAuth 2.0 error.
Consent step
When submitting the end-user’s consent to the Connect2id server:
-
The consented
scopemust include only theopenidscope value. -
The
long_livedflag must be set tofalse(consent not persisted). -
All other consent parameters can be left at their default settings.
Example consent:
{
"scope" : [ "openid" ],
"long_lived" : false
}
8. Troubleshooting
Entra ID error codes
Microsoft provides a form where developers can obtain up-to-date information about error codes returned by Entra ID.
Tips:
-
AADSTS900612:- Message: Failed to parse provider signing keys.
- Solution: Make sure the Connect2id server publishes only RSA keys.
-
AADSTS500126:- Message: External ID token from issuer ‘{issuer}’ failed signature verification. KeyID of token is ‘{identifier}’.
- Solution: Make sure the published RSA keys include an
X.509 certificate (
x5c) and a SHA-1 thumbprint of the certificate (x5t).
Metadata and key caching in Entra ID
Microsoft Entra ID caches the metadata and JWK set of an external MFA provider. Changes to the provider configuration or keys may therefore not take effect immediately.