How to implement EAP-TLS Authentication with Microsoft EntraID
Overview
This section shows a concise, practical approach to validate EAP‑TLS client certificates against Microsoft Entra ID (Azure AD) during a RADIUS EAP‑TLS exchange. It is based on SpherAAA extracting the client certificate field into an object and calling the EntraIDAuth.checkUser() method against EntraID.
Before using certificate-based EAP‑TLS for ongoing authentications, users must first be onboarded. Onboarding is an initial authentication that requires the user's real password (for example: EAP‑TTLS with PAP or GTC, or EAP‑PEAP with GTC). During onboarding SpherAAA authenticates the supplied credentials against EntraID (e.g., EntraIDAuth.checkCredentials(username, password)). On successful verification SpherAAA will:
- Generate an EAP‑TLS client certificate or configuration package (PEM, PKCS#12, or Apple mobileconfig).
- Embed the chosen identifier/OID (used later as the certificate OU or extension).
- Deliver the certificate/configuration to the user via email (or another agreed delivery mechanism).
After onboarding completes and the client installs the certificate/configuration, subsequent authentications use EAP‑TLS for configured SSID. During those EAP‑TLS exchanges SpherAAA extracts the certificate identifier (OU/OID) from the presented client certificate and validates the account state by calling EntraIDAuth.checkUser(oid). If EntraID reports the user as active and the certificate mapping is valid (optinally), access is allowed; otherwise the request is rejected.
Implementers should ensure secure handling of secrets and certificates, record the mapping used during onboarding for later validation, and test the onboarding and EAP‑TLS flows in a staging tenant before production rollout.
Prerequisites
- An registered application in Entra ID with Graph API permissions to read users (client credentials flow).
- CA certificate that was imported or generated in SpherAAA PKI (the CA only needs to be trusted by SpherAAA PKI for interoperability; it does not need to be trusted by EntraID).
High-level flow
- Client performs EAP-TTLS with GTC or PAP (or EAP-PEAP with GTC)
- PolicyLogic verifies that authentication was done using credentials (username and password exists) and using
EntraIDAuth.checkCredentials(username,password)method, sends request to EntraID. - Upon success result from
EntraIDAuth.checkCredentials, SpherAAA responds with Access-Accept to Access-Point. - At the same time, SpherAAA uses the OID from EntraID response and calls pkiCertGen(oid), which creates an EAP-TLS certificate, uses the OID value as the OU field on the certificate, and sends that certificate configuration or payload to the client's email. The email is already known from the username or EntraID response.
- Configuration could be: Full chain PEM, PKCS#12 (for Android, Linux, etc.), or MobileConfig file for Apple products.
- After installing the configuration, Client performs EAP‑TLS and presents the client certificate to the RADIUS server.
- RADIUS script extracts the certificate OU field from the subject and PolicyLogic queries Microsoft Graph using
EntraIDAuth.checkUser(oid)function to verify that this user is still active. - If the response is success, SpherAAA allows access and, based on the response field, SpherAAA could set different VLANs or PolicyGroups.
- Otherwise, reject the access request.
Entra ID registration
- Sign in to the Azure portal with an administrator account and open Azure Active Directory.
- In the left menu choose App registrations → New registration.
- Complete the registration form:
- Name:
spheraaa - Supported account types: Accounts in this organizational directory only (Single tenant)
- Redirect URI: (leave empty)
- Click Register.
- Copy the Application (client) ID and the Directory (tenant) ID and store them securely.
- Go to Certificates & secrets → New client secret.
- Description:
spheraaa-client - Copy the secret value immediately (it cannot be retrieved later). Record its expiration and plan rotation.
- API permissions → Add a permission → Microsoft Graph → Application permissions:
- Add
Directory.Read.All - Click Add permissions.
- Under API permissions, click Grant admin consent for
and confirm. Wait for confirmation. - Verify delegated permission
User.Read(usually present by default) if you plan to use delegated flows.
Security notes: - Restrict the App registration to the minimum permissions required. - Use client credentials (application permissions) for server-to-server calls; avoid delegated flows for automated RADIUS checks. - Record where you store the client ID, tenant ID, and client secret and enforce rotation.
SpherAAA Vault
After registering the app, add the credentials to the SpherAAA Vault so PolicyLogic can call Microsoft Graph.
- In SpherAAA UI: Collections → Vault → Create Entry.
-
Create these vault entries (keys are the default names used by PolicyLogic examples):
-
Key name:
client_id
Key value:<your-client-id>
Expires: optional
Note: Azure Application (client) ID -
Key name:
tenant_id
Key value:<your-tenant-id>
Expires: optional
Note: Azure Directory (tenant) ID -
Key name:
client_secret
Key value:<your-client-secret>
Expires: recommended (set an expiration)
Note: Application secret, keep this encrypted and rotate regularly -
Save each entry and verify PolicyLogic can read them in a test run.
Example SpherAAA validation hook (replace placeholders)
function getVaultCredentials() {
var clientId = Vault.read("entraid-clientid");
var tenantId = Vault.read("entraid-tenantid");
var clientSecret = Vault.read("entraid-key");
if (!clientId || !tenantId || !clientSecret) {
ai
}
return { clientId: clientId, tenantId: tenantId, clientSecret: clientSecret };
}
function buildAuthClient(creds) {
var auth = new EntraIDAuth();
auth.client_id = creds.clientId;
auth.tenant_id = creds.tenantId;
auth.client_secret = creds.clientSecret;
return auth;
}
function verifyEntraId(username, password) {
// credentials flow: email + password present
var shouldGenerateCert = isEmail(username) && password !== undefined && password !== null && password !== "";
var authClient;
try {
var creds = getVaultCredentials();
authClient = buildAuthClient(creds);
} catch (err) {
log("EntraID vault error: " + err.message);
return false;
}
var response;
try {
if (shouldGenerateCert) {
response = authClient.checkCredentials(username, password);
} else {
response = authClient.checkUser(username);
}
} catch (err) {
log("EntraID API error: " + err.message);
return false;
}
log(response);
var authenticated = response && response.exists ? true : false;
if (!authenticated) {
return false;
}
// Set RADIUS reply and optionally generate cert
try {
if (shouldGenerateCert) {
radius.reply['Reply-Message'] = ['UP Auth OK'];
try {
generateCert(username);
} catch (certErr) {
log("Certificate generation error: " + certErr.message);
// policy decision: continue or return false
}
} else {
radius.reply['Reply-Message'] = ['OID Auth OK'];
}
} catch (err) {
// Defensive: some RADIUS bindings may throw on reply assignment
log("Radius reply error: " + err.message);
}
return true;
}
function generateCert(email, opts) {
opts = opts || {};
var defaults = {
ct: "US",
st: "NY",
city: "NewYork",
o: "Company",
ou: "OrganisationUnit",
cn: email || "anonymous@company.com",
days: "365",
passphrase: "nopass",
comment: "EAP-TLS Client Cert",
email_send_as_file: email,
email_send_as_url: false,
ssid: "Secure-Wifi", // Your SSID.
ca_id: "687aa36926d95d8920665d72",
cert_type: "apple_mobileconfig" // supported: "pem","p12","apple_mobileconfig"
};
// Merge defaults into request (opts overrides defaults)
var certRequest = {};
for (var k in defaults) {
certRequest[k] = defaults[k];
}
for (var k2 in opts) {
certRequest[k2] = opts[k2];
}
// pkiGenCert is expected to be available in the runtime
pkiGenCert(certRequest);
}
Notes - Replace getGraphAccessToken() with a secure implementation; cache tokens to avoid throttling. - Tighten certificate validation (issuer, validity period, CRL/OCSP) per your security policy. - If you store certificate thumbprints in user extensions, validate them to prevent certificate reuse. - Test with a staging tenant before production rollout.