Skip to content

PolicyLogic Examples

All examples should be used as references. Depending on the NAS, you may need to add or remove AVPs in your implementation. 

Disconnect-Message OR Change-Of-Authorization

function getExistingSession() {
  // Query for existing active entries (assuming that Accounting records stored on the "sessions" colllection)
  var session = Collection.get("Calling-Station-Id", radius.request['Calling-Station-Id'], "sessions");
  // Check for result
  if (session){
    // Store NAS-IP-Address for Disconnect or Change-Of-Authorization requests
    var nas_ip = session['NAS-IP-Address'];
    if (nas_ip) {
      sendDM(nas_ip);
      // sendCoA(coa_avps);
    }
  // Entry is not found
  } else {
  // Create Reply-Message
  radius.reply['Reply-Message'] = "No active sessions found";
  // Return fail response
  fail();
  }

}

function sendDM(nas_ip) {
  // Before building DM or CoA request, save existing AVP's
  // into "old_request" group
  radius.old_request = radius.request 
  // Clean "request" group
  radius.request = {};
  // Add Calling-Station-Id to "request" group
  radius.request['Calling-Station-Id'] = radius.old_request['Calling-Station-Id'];
  // Send Disconnect-Request
    var r = RadiusRequest("DM-REQUEST", nas_ip, 3799, "3000");
  // Return result of request
  return r;
}

function sendCoA(avps) {
  // Before building DM or CoA request, save existing AVP's
  // into "old_request" group
  radius.old_request = radius.request 
  // Clean "request" group
  radius.request = {};
  // Add Calling-Station-Id to "request" group
  radius.request['Calling-Station-Id'] = radius.old_request['Calling-Station-Id'];
  // Send Disconnect-Request
    var r = RadiusRequest("COA-REQUEST", nas_ip, 3799, "3000");
  // Return result of request
  return r;
}

Send PPSK (DPSK) response with dynamic vlan.

// Check for PPSK Auth
    //
    function checkPPSK() {

      if (radius.request['User-Name'] == radius.request['User-Password']) {
        var dbResult = queryMany("Called-Station-Id", radius.request['Called-Station-Id'], "ppsk");
        if (dbResult) {
          for (i = 0; i <= dbResult.length - 1; i++) {
            radius.reply['tag'][i] = {};
            radius.reply['tag'][i]['Tunnel-Password'] = dbResult[i]['Tunnel-Password'];
            radius.reply['tag'][i]['Tunnel-Private-Group-Id'] = dbResult[i]['Tunnel-Private-Group-Id'];
            radius.reply['tag'][i]['Tunnel-Type'] = dbResult[i]['Tunnel-Type'];
            radius.reply['tag'][i]['Tunnel-Medium-Type'] = dbResult[i]['Tunnel-Medium-Type'];
          }

          radius.reply['Reply-Message'] = "Success Auth";

          //
          // Store request in the "Session" collection with TTL 1hr
          //
          // function: Collection.upsert(collectionName, object, TTL, indexName, indexValue);
          //      
          Collection.upsert("sessions", radius.request, 3600, "Calling-Station-Id", radius.request['Calling-Station-Id']);

          success();
          return true;
        }
        return false;
      }
    }

Send Radius request to different AAA (proxy the request)

//
// Determine whether the request has to be proxied to a partner network.
// getProxySettings(realm_or_identity) is internal function.
//
function proxyCheck() {
  const proxySettings = getProxySettings(radius.user['Realm']);
    if (proxySettings) {
        // Parameters for this realm is exists on the "config" collections. 
        const proxyInstance = new RadiusProxy();

        // Or set parameters manually.
        // proxyInstance.code = radius.code;
        // proxyInstance.dst = "1.2.3.4"; //radius.proxy['Proxy-Address'] = "1.2.3.4";
        // proxyInstance.dst_port = "1812"; // radius.proxy['Proxy-Address-Port'] = "1812";
        // proxyInstance.timeout = "2000";
        // proxyInstance.name = "MY_PROXY_PROVIDER";
        // proxyInstance.timeout_unreachable = "10000";
        // proxyInstance.balance_mode = "round-robin";
        // proxyInstance.dpd = "false";

        var  proxyResult = proxyInstance.send();
        if (proxyResult)
          return proxyResult;
    }
    return false;
}

Limit authentication by device MAC address or username

Bind a username to specific MAC addresses

Restrict a username to a known set of devices. Each allowed pair is stored in a allowed_devices collection as { "User-Name": ..., "Calling-Station-Id": ... }; a request is accepted only if the incoming Calling-Station-Id matches one of the entries stored for that User-Name.

function checkAllowedDevice() {
  var username = radius.request['User-Name'];
  var mac = radius.request['Calling-Station-Id'];

  var result = Collection.search("allowed_devices", "User-Name", username);
  if (!result.ok || result.meta.count === 0) {
    fail("Username is not authorized for any device");
    return false;
  }

  var allowed = result.data.some(function (entry) {
    return entry['Calling-Station-Id'] === mac;
  });

  if (!allowed) {
    fail("This device is not authorized for this username");
    return false;
  }

  success();
  return true;
}

Populate allowed_devices in advance (via the API or the Collections page), or let PolicyLogic add a device automatically the first time a username authenticates, effectively pinning it to that device from then on:

function bindFirstDevice() {
  var username = radius.request['User-Name'];
  var mac = radius.request['Calling-Station-Id'];

  var result = Collection.search("allowed_devices", "User-Name", username);

  if (result.ok && result.meta.count > 0) {
    var allowed = result.data.some(function (entry) {
      return entry['Calling-Station-Id'] === mac;
    });
    if (!allowed) {
      fail("This username is already bound to a different device");
      return false;
    }
  } else {
    // First time we see this username: bind it to the current device.
    Collection.add("allowed_devices", { "User-Name": username, "Calling-Station-Id": mac }, 0);
  }

  success();
  return true;
}

Cap the number of distinct devices per username

Instead of a fixed allow-list, let each username register up to a fixed number of devices and reject any device beyond that limit. Known devices are stored in a user_devices collection with a TTL, so a device that stops being used eventually drops out of the count.

var MAX_DEVICES_PER_USER = 3;
var DEVICE_TTL = 2592000; // 30 days

function checkDeviceLimit() {
  var username = radius.request['User-Name'];
  var mac = radius.request['Calling-Station-Id'];

  var result = Collection.search("user_devices", "User-Name", username);
  var knownDevices = result.ok ? result.data : [];

  var alreadyKnown = knownDevices.some(function (entry) {
    return entry['Calling-Station-Id'] === mac;
  });

  if (!alreadyKnown && knownDevices.length >= MAX_DEVICES_PER_USER) {
    fail("Maximum number of devices reached for this username");
    return false;
  }

  if (!alreadyKnown) {
    Collection.add("user_devices", { "User-Name": username, "Calling-Station-Id": mac }, DEVICE_TTL);
  }

  success();
  return true;
}

MAC Authentication Bypass (MAB)

For devices that can't run 802.1X (printers, cameras, some IoT devices), authenticate by Calling-Station-Id alone against a pre-registered device list, and assign a VLAN from that record.

function checkMAB() {
  var mac = radius.request['Calling-Station-Id'];

  // "mab_devices" holds one entry per known device, e.g.
  // { "Calling-Station-Id": "aa:bb:cc:dd:ee:ff", "Vlan": "20" }
  var result = Collection.search("mab_devices", "Calling-Station-Id", mac);

  if (!result.ok || result.meta.count === 0) {
    fail("Unknown device, MAC Authentication Bypass denied");
    return false;
  }

  var device = result.data[0];
  radius.reply['Tunnel-Type'] = "13";         // RFC 2868: 13 = VLAN
  radius.reply['Tunnel-Medium-Type'] = "6";   // RFC 2868: 6 = IEEE-802
  radius.reply['Tunnel-Private-Group-Id'] = device['Vlan'];

  success();
  return true;
}

Lock out an account after repeated failed logins

Count failed attempts per username in a TTL'd collection, and reject further attempts once a threshold is reached within the window, even if the credentials supplied are correct. The counter resets on a successful login.

var MAX_FAILED_ATTEMPTS = 5;
var LOCKOUT_WINDOW = 900; // 15 minutes

function checkCredentialsWithLockout() {
  var username = radius.request['User-Name'];

  var lockout = Collection.search("failed_logins", "User-Name", username);
  if (lockout.ok && lockout.meta.count > 0 && lockout.data[0]['Count'] >= MAX_FAILED_ATTEMPTS) {
    fail("Account temporarily locked due to repeated failed logins");
    return false;
  }

  // Replace with your actual credential check.
  var validCredentials = radius.request['User-Password'] === "expected-value";

  if (!validCredentials) {
    recordFailedAttempt(username);
    fail("Invalid username or password");
    return false;
  }

  Collection.remove("failed_logins", "User-Name", username);
  success();
  return true;
}

function recordFailedAttempt(username) {
  var existing = Collection.search("failed_logins", "User-Name", username);
  var count = (existing.ok && existing.meta.count > 0) ? existing.data[0]['Count'] + 1 : 1;
  Collection.upsert("failed_logins", { "User-Name": username, "Count": count }, LOCKOUT_WINDOW, "User-Name", username);
}

Limit concurrent sessions per user

Track active sessions from accounting records, and either reject a new login or disconnect the oldest session once a user exceeds a fixed number of concurrent devices. Acct-Status-Type is a standard RADIUS accounting attribute (RFC 2866); it passes through into radius.request the same way as any other AVP.

var MAX_CONCURRENT_SESSIONS = 2;
var SESSION_TTL = 86400; // safety net in case a Stop record is ever lost

// Accounting entry point: keep the "active_sessions" collection in sync.
function trackSession() {
  var status = radius.request['Acct-Status-Type'];
  var sessionId = radius.request['Acct-Session-Id'];

  if (status === "Start" || status === "Interim-Update") {
    Collection.upsert("active_sessions", {
      "Acct-Session-Id": sessionId,
      "User-Name": radius.request['User-Name'],
      "Calling-Station-Id": radius.request['Calling-Station-Id'],
      "NAS-IP-Address": radius.request['NAS-IP-Address'],
      "Started": Date.now()
    }, SESSION_TTL, "Acct-Session-Id", sessionId);
  } else if (status === "Stop") {
    Collection.remove("active_sessions", "Acct-Session-Id", sessionId);
  }

  success();
}

// Auth entry point: enforce the limit before accepting a new session.
function checkSessionLimit() {
  var username = radius.request['User-Name'];
  var mac = radius.request['Calling-Station-Id'];

  var result = Collection.search("active_sessions", "User-Name", username);
  var sessions = result.ok ? result.data : [];

  // Reconnecting on the same device doesn't count as a new session.
  var sameDevice = sessions.some(function (s) { return s['Calling-Station-Id'] === mac; });

  if (!sameDevice && sessions.length >= MAX_CONCURRENT_SESSIONS) {
    // Pick one strategy: disconnect the oldest session to make room,
    // or reject the new request outright.
    var oldest = sessions.reduce(function (a, b) {
      return a['Started'] < b['Started'] ? a : b;
    });
    kickSession(oldest['NAS-IP-Address'], oldest['Calling-Station-Id']);
    // fail("Maximum concurrent sessions reached");
    // return false;
  }

  success();
  return true;
}

function kickSession(nas_ip, mac) {
  radius.old_request = radius.request;
  radius.request = {};
  radius.request['Calling-Station-Id'] = mac;
  return RadiusRequest("DM-REQUEST", nas_ip, 3799, "3000");
}

Proxy to a partner realm with failover

Send the request to a primary AAA server, and fall back to a secondary one if the primary doesn't respond. Builds on the realm-based proxy example above.

function proxyWithFailover() {
  var primary = new RadiusProxy();
  primary.dst = "10.0.0.1";
  primary.dst_port = "1812";
  primary.timeout = "2000";
  primary.name = "PRIMARY_AAA";
  primary.secret = Vault.read("primary_secret");

  var result = primary.send();

  if (!result) {
    log("Primary AAA server did not respond, failing over to secondary");
    var secondary = new RadiusProxy();
    secondary.dst = "10.0.0.2";
    secondary.dst_port = "1812";
    secondary.timeout = "2000";
    secondary.name = "SECONDARY_AAA";
    secondary.secret = Vault.read("secondary_secret");
    result = secondary.send();
  }

  return result !== undefined && result !== null ? result : false;
}

Assign a VLAN by device vendor

Look up the connecting device's NIC vendor from its MAC OUI, and route unrecognized vendors to a quarantine VLAN instead of the main network.

var CORPORATE_VENDORS = ["Dell Inc.", "Apple, Inc.", "Lenovo"];
var CORPORATE_VLAN = "10";
var QUARANTINE_VLAN = "99";

function assignVlanByVendor() {
  var vendor = getVendor(radius.request['Normalized-Mac']);
  var vlan = CORPORATE_VENDORS.indexOf(vendor) !== -1 ? CORPORATE_VLAN : QUARANTINE_VLAN;

  radius.reply['Tunnel-Type'] = "13";        // RFC 2868: 13 = VLAN
  radius.reply['Tunnel-Medium-Type'] = "6";  // RFC 2868: 6 = IEEE-802
  radius.reply['Tunnel-Private-Group-Id'] = vlan;

  success();
  return true;
}

Restrict access to a time window

Allow an account to authenticate only during a fixed window, for example a contractor or guest account limited to business hours. The server evaluates Date() in its own local timezone, so confirm what that is before relying on this in production.

var ALLOWED_START_HOUR = 8;  // 08:00
var ALLOWED_END_HOUR = 18;   // 18:00

function checkBusinessHours() {
  var now = new Date();
  var day = now.getDay();   // 0 = Sunday ... 6 = Saturday
  var hour = now.getHours();

  var isWeekday = day >= 1 && day <= 5;
  var isWithinHours = hour >= ALLOWED_START_HOUR && hour < ALLOWED_END_HOUR;

  if (!isWeekday || !isWithinHours) {
    fail("Access is only allowed on weekdays between 08:00 and 18:00");
    return false;
  }

  success();
  return true;
}

Validate certificate fields against an expected binding

Beyond checking that a certificate is valid, confirm one of its fields (here, the OU) matches what's expected for that account before granting access. Combines certificate revocation status with parseDN.

function verifyCertificateBinding() {
  if (radius.request['Inner-Auth-Method'] !== "X509") {
    fail("Certificate not found");
    return false;
  }

  // Confirm the certificate isn't revoked: use OCSP if it's enabled for this
  // certificate, otherwise fall back to SpherAAA's internal PKI status.
  var certStatus = radius.user.X509.OCSP_Response;
  if (certStatus) {
    if (certStatus !== "good") {
      fail("Certificate rejected, OCSP status: " + certStatus);
      return false;
    }
  } else if (!checkTLSCert()) {
    fail("Certificate is revoked or unknown");
    return false;
  }

  // Confirm the certificate's OU matches the department on file for this account.
  var subject = parseDN(radius.user.X509.SubjectDN);
  var expected = Collection.search("department_certs", "User-Name", radius.user['Identity']);

  if (!expected.ok || expected.meta.count === 0 || expected.data[0]['OU'] !== subject.OU) {
    fail("Certificate OU does not match the expected department");
    return false;
  }

  success();
  return true;
}

RFC5580

    radius.request['Location-Capable'] = "NAS-Location";
    radius.request['Location-Data'] = {
      'Index': '1',
      'Location': {
        'city': 'Chicago',
        'state': 'IL',
        'zip': '60603',
        'nam': 'NAM_Value',
        'plc': 'PLC_Value',
        'loc': 'LOC_Value',
        'lmk': 'LMK_Value'
      },
      'Country': 'US'
    };

    radius.request['Location-Information'] = {
      'Index': '1',
      'Code': '0',
      'Entity': '1',
      'Sighting-Time': convertToNTP64(Date.now()),
      'Time-To-Live': convertToNTP64(Date.now() + (43200*1000)),
      'Method': 'Manual'
    }

Call function in the new thread or parallel

function acct_handler() {

    //
    // To avoid delaying the Accounting-Response, execute the Session update function in parallel.
    // So first return Acct-Response
    success();
    //
    // and then insert / update "sessions" collection
    //
    runParallel('acct()', 500);
  }

  //
  // Update sessions collection.
  //
  function acct() {
    // function: Collection.upsert(collectionName, object, TTL, indexName, indexValue);
    Collection.upsert("sessions", radius.request, 3600, "Calling-Station-Id", radius.request['Calling-Station-Id']);
  }

Issue EAP-TLS certificate by calling SpherAAA API

function issue_cert() {
  var ca_id = "6461706ef4befabdc81e6d19";
  var cert_type = "pem";
  var url = "https://aaa.spheralogic.com/api/pki/gen/?ca_id="+ca_id+"&cert_type="+cert_type

  var headers = {}
  headers["accept"] = "application/json"
  headers["Content-Type"] = "application/json"
  headers["Authorization"] = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."

  var httpclient = new HTTPClient();
  httpclient.url = url;
  httpclient.headers = headers;
  httpclient.method = "POST";

  var data = {
    "ct": "US",
    "st": "NY",
    "city": "NewYork",
    "o": "Company",
    "ou": "USER_NAME/ID/Department_Name",
    "cn": "anonymous@company.com",
    "days": "365",
    "passphrase": "nopass",
    "comment": "EAP-TLS Client Cert",
    "email_send_as_file": false,
    "email_send_as_url": false,
    "ssid": "nossid"
  };
  httpclient.data = JSON.stringify(data);

  try {
    var response = httpclient.send();
    log("httpclient result: " + JSON.stringify(response));
  } catch (e) {
    log("Recived error: " + e.toString());
  }
}

Insert data to the Elasticsearch or OpenSearch (HTTP Basic Auth)

function ops_insert() {
  var url = "https://opensearch.domain.com:9200/my_index/_doc"
  var headers = {}
  var auth = Base64.encode("user:password")
  headers["Content-Type"] = "application/json"
  headers["Authorization"] = "Basic "+ auth
  var httpclient = new HTTPClient();
  httpclient.url = url;
  httpclient.headers = headers;
  httpclient.method = "POST";
  httpclient.disableSslVerification = false; 
  var data = {"sourcetype":"auth", "data":radius}
  //var data = {"sourcetype":"acct", "data":radius}
  httpclient.data = JSON.stringify(data);

  try {
    var response = httpclient.send();
    log("httpclient result: " + JSON.stringify(response));
  } catch (e) {
    log("Recived error: " + e.toString());
  }
}

EAP-TLS/TTLS/PEAP 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

  1. Client performs EAP-TTLS with GTC or PAP (or EAP-PEAP with GTC)
  2. PolicyLogic verifies that authentication was done using credentials (username and password exists) and using EntraIDAuth.checkCredentials(username,password) method, sends request to EntraID.
  3. Upon success result from EntraIDAuth.checkCredentials, SpherAAA responds with Access-Accept to Access-Point.
  4. 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.
  5. Configuration could be: Full chain PEM, PKCS#12 (for Android, Linux, etc.), or MobileConfig file for Apple products.
  6. After installing the configuration, Client performs EAP‑TLS and presents the client certificate to the RADIUS server.
  7. 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.
  8. If the response is success, SpherAAA allows access and, based on the response field, SpherAAA could set different VLANs or PolicyGroups.
  9. Otherwise, reject the access request.

Entra ID registration

  1. Sign in to the Azure portal (https://portal.azure.com) with an account that has at least Application Administrator + consent rights. Open Microsoft Entra ID.
  2. Go to: App registrations → New registration. Enter:

  3. Name: spheraaa
    Supported account types: Accounts in this organizational directory only (Single tenant)
    Redirect URI: leave empty
    Click Register

  4. On the Overview page copy and securely store:
    Application (client) ID
    Directory (tenant) ID

  5. Create a client secret: Certificates & secrets → Client secrets → New client secret.
    Description: spheraaa-client
    Expiration: choose shortest that fits rotation policy
    After creation copy the Secret Value immediately (cannot be retrieved later)

  6. Add Microsoft Graph application permission:
    API permissions → Add a permission → Microsoft Graph → Application permissions
    Select: Directory.Read.All
    Add permissions

  7. Grant admin consent:
    API permissions → Grant admin consent for → Confirm
    Verify Status = Granted for Directory.Read.All

Security notes:
* Store client_id, tenant_id, and client_secret only in Vault entries (never in source).
* Enforce secret rotation before expiry (calendar reminder + automation if possible).
* Keep permissions minimal (only Directory.Read.All).
* Review Entra audit logs after granting consent.
* Remove unused app registrations periodically.

SpherAAA Vault

After registering the app, add the credentials to the SpherAAA Vault so PolicyLogic can call Microsoft Graph.

  1. In SpherAAA UI: Collections → Vault → Create Entry.
  2. Create these vault entries (keys are the default names used by PolicyLogic examples):

  3. Key name: client_id
    Key value: <your-client-id>
    Expires: optional
    Note: Azure Application (client) ID

  4. Key name: tenant_id
    Key value: <your-tenant-id>
    Expires: optional
    Note: Azure Directory (tenant) ID

  5. Key name: client_secret
    Key value: <your-client-secret>
    Expires: recommended (set an expiration)
    Note: Application secret, keep this encrypted and rotate regularly

  6. Save each entry and verify PolicyLogic can read them in a test run.

Example script for PolicyLogic.

// Override this function on your main file. 
function verifyEapTls() {
  if (radius.request['Inner-Auth-Method'] === "X509") {
    // Check whether to perform OID-based authentication using EntraID.
    // Verify that the EAP identity is "anonymous@company.com",
    // which indicates the EAP-TLS certificate was issued during onboarding
    // (for example, via EAP-TTLS).
    if (radius.request['User-Name'] === "anonymous@company.com") {
      const subjectObjects = parseDN(radius.user.X509.SubjectDN);
      const entraResult = verifyEntraId(subjectObjects.OU);
      if (entraResult) {
        return true;
      }
    } else if (checkTLSCert()) {
      return true;
    } else {
      radius.reply['Reply-Message'] = "Certificate not found";
      return false;
    }
  } else {
    return false;
  }
}

// Verify credentials against EntraID
// more info https://spheralogic.com/wiki/policy/#entraidauth
function verifyEntraId(username, password) {

  // Read credentials from Vault
  const clientId = Vault.read("entraid-clientid");
  const tenantId = Vault.read("entraid-tenantid");
  const clientSecret = Vault.read("entraid-key");

  // Flag for cert generation
  var genCert = false;

  // Validate required parameters
  if (!clientId || !tenantId || !clientSecret) {
    throw new Error("Missing EntraID credentials in Vault");
  }

  // Initialize EntraIDAuth object
  const authClient = new EntraIDAuth();

  // Set parameters for the auth client
  authClient.client_id = clientId;
  authClient.tenant_id = tenantId;
  authClient.client_secret = clientSecret;

  // Attempt authentication
  var response;
  if (isEmail(username) && password !== undefined) {
    response = authClient.checkCredentials(username, password);
    genCert = true;
  } else
    response = authClient.checkUser(username);

  // Log for debug
  //log(response);

  const result = !!(response && response.exists);
  if (result) {
    if (genCert) {
      radius.reply['Reply-Message'] = ['UP Auth OK'];
      generateCert(response);
    } else
      radius.reply['Reply-Message'] = ['OID Auth OK'];
  }

  // Return true if authentication succeeded (user exists and access_token decoded)
  return result;
}

function generateCert(result) {
  log(result)
  var cert_request = {
    "ct": "US",
    "st": "NY",
    "city": "NewYork",
    "o": "Company",
    "ou": result.oid,
    "cn": "anonymous@company.com",
    "days": "365",
    "passphrase": "nopass",
    "comment": "EAP-TLS Client Cert",
    "email_send_as_file": result.upn,
    "email_send_as_url": false,
    "ssid": "MySecureWiFi", // Replace with your 802.1x (WPA-Ent) SSID.
    "ca_id": "687aa36926d95d8920665d72", // CA certificate ID.
    "cert_type": "pem" //cert_type: Supported formats: "pem","p12", "apple_mobileconfig".
  };

  // Log for debug
  // log(cert_request) 

  pkiGenCert(cert_request);
}
Back to top