Add certificate authentication, in-app setup guides and security audit
Certificate (RFC 7523 client assertion) as an alternative to the client secret: one-click generation of a 3072-bit RSA key pair with a self-signed certificate, .cer download (public part only), own PEM upload with validation, expiry display, encrypted key storage. Both the authorization code exchange and the Graph client-credentials request use the selected method. Step-by-step guides for secret, certificate and the app registration are shown in the settings. Security audit (docs/security-audit.md) and fixes: - Multi-tenant mode ignored the unverified email claim: matching now uses the UPN only, or the email claim when xms_edov is true. - Login starts are rate limited per client (30 per 10 minutes). - Optional trusted proxy header for client IPs (M365_LOGIN_CLIENT_IP_HEADER / filter). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJxAHYdMfKPoN4koRc4Ci2
This commit is contained in:
parent
966164177d
commit
8766927123
19 changed files with 2515 additions and 644 deletions
|
|
@ -16,6 +16,8 @@ class M365_Login_Admin {
|
|||
const GROUP = 'm365_login';
|
||||
const AJAX_TEST = 'm365_login_test_connection';
|
||||
const AJAX_GROUPS = 'm365_login_search_groups';
|
||||
const AJAX_CERT = 'm365_login_certificate';
|
||||
const POST_CERT = 'm365_login_download_cert';
|
||||
const NONCE_TEST = 'm365_login_test';
|
||||
|
||||
/**
|
||||
|
|
@ -63,6 +65,8 @@ class M365_Login_Admin {
|
|||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
|
||||
add_action( 'wp_ajax_' . self::AJAX_TEST, array( $this, 'ajax_test_connection' ) );
|
||||
add_action( 'wp_ajax_' . self::AJAX_GROUPS, array( $this, 'ajax_search_groups' ) );
|
||||
add_action( 'wp_ajax_' . self::AJAX_CERT, array( $this, 'ajax_certificate' ) );
|
||||
add_action( 'admin_post_' . self::POST_CERT, array( $this, 'download_certificate' ) );
|
||||
add_action( 'update_option_' . M365_LOGIN_OPTION, array( $this->graph, 'flush_token' ) );
|
||||
add_action( 'admin_notices', array( $this, 'setup_notice' ) );
|
||||
}
|
||||
|
|
@ -140,6 +144,7 @@ class M365_Login_Admin {
|
|||
'nonce' => wp_create_nonce( self::NONCE_TEST ),
|
||||
'action' => self::AJAX_TEST,
|
||||
'groupAction' => self::AJAX_GROUPS,
|
||||
'certAction' => self::AJAX_CERT,
|
||||
'defaultLogo' => M365_Login_Button::microsoft_logo(),
|
||||
'i18n' => array(
|
||||
'chooseIcon' => __( 'Choose button icon', 'm365-login' ),
|
||||
|
|
@ -154,6 +159,9 @@ class M365_Login_Admin {
|
|||
'remove' => __( 'Remove', 'm365-login' ),
|
||||
'saveFirst' => __( 'Save the connection settings first, then search for groups.', 'm365-login' ),
|
||||
'confirmKey' => __( 'Generate a new fallback key on save? The old link stops working.', 'm365-login' ),
|
||||
'generating' => __( 'Generating a 3072-bit key pair, this takes a moment…', 'm365-login' ),
|
||||
'confirmCert' => __( 'Replace the stored certificate? Sign-in stops working until the new certificate is uploaded to Entra ID.', 'm365-login' ),
|
||||
'confirmCertRemove' => __( 'Remove the stored certificate when saving? Sign-in with the certificate method stops working.', 'm365-login' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
|
@ -224,6 +232,63 @@ class M365_Login_Admin {
|
|||
wp_send_json_success( array( 'groups' => $groups ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: generate a new self-signed certificate and store it (key encrypted).
|
||||
*/
|
||||
public function ajax_certificate() {
|
||||
check_ajax_referer( self::NONCE_TEST, 'nonce' );
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'You are not allowed to do this.', 'm365-login' ) ), 403 );
|
||||
}
|
||||
$op = isset( $_POST['op'] ) ? sanitize_key( wp_unslash( $_POST['op'] ) ) : '';
|
||||
if ( 'generate' !== $op ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Unknown operation.', 'm365-login' ) ) );
|
||||
}
|
||||
|
||||
$host = wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
$pair = M365_Login_Certificate::generate( is_string( $host ) ? $host : 'wordpress' );
|
||||
if ( is_wp_error( $pair ) ) {
|
||||
wp_send_json_error( array( 'message' => $pair->get_error_message() ) );
|
||||
}
|
||||
$stored = $this->settings->store_certificate( $pair );
|
||||
if ( is_wp_error( $stored ) ) {
|
||||
wp_send_json_error( array( 'message' => $stored->get_error_message() ) );
|
||||
}
|
||||
$this->graph->flush_token();
|
||||
|
||||
$info = M365_Login_Certificate::info( $pair['certificate'] );
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'Certificate generated and stored. Download the .cer file and upload it in Entra ID.', 'm365-login' ),
|
||||
'thumbprint' => $info ? $info['thumbprint'] : '',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the public certificate as a .cer download (never the private key).
|
||||
*/
|
||||
public function download_certificate() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You are not allowed to do this.', 'm365-login' ), 403 );
|
||||
}
|
||||
check_admin_referer( self::POST_CERT );
|
||||
|
||||
$pem = $this->settings->certificate_pem();
|
||||
if ( '' === $pem ) {
|
||||
wp_die( esc_html__( 'No certificate is stored.', 'm365-login' ), 404 );
|
||||
}
|
||||
$host = wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
$name = 'm365-login-' . sanitize_file_name( is_string( $host ) ? $host : 'wordpress' ) . '.cer';
|
||||
|
||||
nocache_headers();
|
||||
header( 'Content-Type: application/x-x509-ca-cert' );
|
||||
header( 'Content-Disposition: attachment; filename="' . $name . '"' );
|
||||
header( 'Content-Length: ' . strlen( $pem ) );
|
||||
echo $pem; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- PEM text, public certificate only.
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the settings screen.
|
||||
*/
|
||||
|
|
@ -235,6 +300,8 @@ class M365_Login_Admin {
|
|||
$s = $this->settings->all();
|
||||
$configured = $this->settings->is_configured();
|
||||
$has_secret = '' !== $this->settings->client_secret();
|
||||
$method = $this->settings->auth_method();
|
||||
$cert_info = $this->settings->certificate_info();
|
||||
$option = M365_LOGIN_OPTION;
|
||||
$field = function ( $key ) use ( $option ) {
|
||||
return esc_attr( $option . '[' . $key . ']' );
|
||||
|
|
@ -280,6 +347,9 @@ class M365_Login_Admin {
|
|||
<button type="button" class="button" id="m365-test"><?php esc_html_e( 'Test tenant', 'm365-login' ); ?></button>
|
||||
</div>
|
||||
<p class="description"><?php esc_html_e( 'Recommended: the GUID of your tenant. Only sign-ins from this tenant are accepted. "organizations" allows any work or school account.', 'm365-login' ); ?></p>
|
||||
<?php if ( $this->settings->is_multi_tenant() && '' !== $s['tenant_id'] ) : ?>
|
||||
<p class="m365-warning m365-warning--strong"><?php esc_html_e( 'Multi-tenant mode: accounts from any Microsoft tenant can sign in. Their "email" attribute is not verified, so the plugin matches on the user principal name (verified domain) only and ignores the e-mail claim unless Microsoft marks it as domain-verified. Use the e-mail domain allow-list on the Security tab, or better, pin your tenant GUID.', 'm365-login' ); ?></p>
|
||||
<?php endif; ?>
|
||||
<div id="m365-test-result" class="m365-inline-result" hidden></div>
|
||||
</div>
|
||||
|
||||
|
|
@ -289,18 +359,132 @@ class M365_Login_Admin {
|
|||
</div>
|
||||
|
||||
<div class="m365-field">
|
||||
<label for="m365-client-secret"><?php esc_html_e( 'Client secret', 'm365-login' ); ?></label>
|
||||
<div class="m365-field__row">
|
||||
<input type="password" id="m365-client-secret" name="<?php echo $field( 'client_secret' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="" class="regular-text code" autocomplete="new-password" placeholder="<?php echo $has_secret ? esc_attr__( '•••••••••••• (stored, leave empty to keep)', 'm365-login' ) : esc_attr__( 'Paste the secret value', 'm365-login' ); ?>" />
|
||||
<button type="button" class="button m365-toggle-secret" aria-label="<?php esc_attr_e( 'Show secret', 'm365-login' ); ?>"><span class="dashicons dashicons-visibility"></span></button>
|
||||
</div>
|
||||
<?php if ( $has_secret ) : ?>
|
||||
<label class="m365-check m365-check--inline">
|
||||
<input type="checkbox" name="<?php echo $field( 'client_secret_clear' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="1" />
|
||||
<?php esc_html_e( 'Remove the stored secret', 'm365-login' ); ?>
|
||||
<span class="m365-field__label"><?php esc_html_e( 'How should WordPress authenticate to Microsoft?', 'm365-login' ); ?></span>
|
||||
<div class="m365-method">
|
||||
<label class="m365-method__option" data-method="secret">
|
||||
<input type="radio" name="<?php echo $field( 'auth_method' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="secret" <?php checked( $method, 'secret' ); ?> />
|
||||
<span>
|
||||
<strong><?php esc_html_e( 'Client secret', 'm365-login' ); ?></strong>
|
||||
<em><?php esc_html_e( 'Quick to set up. A password-like value created in Entra ID that expires after 6–24 months and must be renewed.', 'm365-login' ); ?></em>
|
||||
</span>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<p class="description"><?php esc_html_e( 'Stored encrypted (AES-256-GCM, key derived from your WordPress salts) and never displayed again. Client secrets expire – note the expiry date in Entra ID.', 'm365-login' ); ?></p>
|
||||
<label class="m365-method__option" data-method="certificate">
|
||||
<input type="radio" name="<?php echo $field( 'auth_method' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="certificate" <?php checked( $method, 'certificate' ); ?> />
|
||||
<span>
|
||||
<strong><?php esc_html_e( 'Certificate', 'm365-login' ); ?><span class="m365-method__badge"><?php esc_html_e( 'Recommended', 'm365-login' ); ?></span></strong>
|
||||
<em><?php esc_html_e( 'The private key never leaves this server; only the public certificate is uploaded to Entra ID. Generated here with one click, valid for 2 years.', 'm365-login' ); ?></em>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Secret -->
|
||||
<div class="m365-auth-panel" data-method="secret">
|
||||
<div class="m365-field">
|
||||
<label for="m365-client-secret"><?php esc_html_e( 'Client secret', 'm365-login' ); ?></label>
|
||||
<div class="m365-field__row">
|
||||
<input type="password" id="m365-client-secret" name="<?php echo $field( 'client_secret' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="" class="regular-text code" autocomplete="new-password" placeholder="<?php echo $has_secret ? esc_attr__( '•••••••••••• (stored, leave empty to keep)', 'm365-login' ) : esc_attr__( 'Paste the secret value', 'm365-login' ); ?>" />
|
||||
<button type="button" class="button m365-toggle-secret" aria-label="<?php esc_attr_e( 'Show secret', 'm365-login' ); ?>"><span class="dashicons dashicons-visibility"></span></button>
|
||||
</div>
|
||||
<?php if ( $has_secret ) : ?>
|
||||
<label class="m365-check m365-check--inline">
|
||||
<input type="checkbox" name="<?php echo $field( 'client_secret_clear' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="1" />
|
||||
<?php esc_html_e( 'Remove the stored secret', 'm365-login' ); ?>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<p class="description"><?php esc_html_e( 'Stored encrypted (AES-256-GCM, key derived from your WordPress salts) and never displayed again. Client secrets expire – note the expiry date in Entra ID.', 'm365-login' ); ?></p>
|
||||
</div>
|
||||
|
||||
<details class="m365-guide" <?php echo $has_secret ? '' : 'open'; ?>>
|
||||
<summary><?php esc_html_e( 'Step-by-step: create a client secret in Entra ID', 'm365-login' ); ?></summary>
|
||||
<div class="m365-guide__body">
|
||||
<ol>
|
||||
<li><?php esc_html_e( 'Open entra.microsoft.com and sign in with an account that has the "Application Administrator" or "Global Administrator" role.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Go to Identity → Applications → App registrations and open your app (or create it first, see the general guide in the sidebar).', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'In the left menu choose Certificates & secrets, then the tab Client secrets, and click New client secret.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Enter a description such as "WordPress login" and pick an expiry. Microsoft allows at most 24 months; put a reminder in your calendar two weeks before.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Click Add. Copy the Value column immediately – it is shown only once. The Secret ID column is NOT what you need.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Paste the value into the Client secret field above and save this page.', 'm365-login' ); ?></li>
|
||||
</ol>
|
||||
<p class="m365-guide__note"><?php esc_html_e( 'When the secret expires, sign-ins fail with "Could not complete the sign-in with Microsoft". Create a new secret, paste it here, save, then delete the old one in Entra ID.', 'm365-login' ); ?></p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Certificate -->
|
||||
<div class="m365-auth-panel" data-method="certificate">
|
||||
<div class="m365-cert">
|
||||
<?php if ( $cert_info ) : ?>
|
||||
<?php
|
||||
$days_left = (int) floor( ( $cert_info['not_after'] - time() ) / DAY_IN_SECONDS );
|
||||
if ( $days_left < 0 ) {
|
||||
$status_class = 'is-bad';
|
||||
$status_text = __( 'Expired', 'm365-login' );
|
||||
} elseif ( $days_left < 30 ) {
|
||||
$status_class = 'is-warn';
|
||||
/* translators: %d: number of days */
|
||||
$status_text = sprintf( __( 'Expires in %d days', 'm365-login' ), $days_left );
|
||||
} else {
|
||||
$status_class = 'is-ok';
|
||||
$status_text = __( 'Valid', 'm365-login' );
|
||||
}
|
||||
?>
|
||||
<span class="m365-cert__status <?php echo esc_attr( $status_class ); ?>"><?php echo esc_html( $status_text ); ?></span>
|
||||
<dl class="m365-cert__grid">
|
||||
<dt><?php esc_html_e( 'Thumbprint (SHA-1)', 'm365-login' ); ?></dt>
|
||||
<dd><code id="m365-cert-thumbprint"><?php echo esc_html( $cert_info['thumbprint'] ); ?></code> <button type="button" class="button button-small m365-copy__button" data-copy="m365-cert-thumbprint"><?php esc_html_e( 'Copy', 'm365-login' ); ?></button></dd>
|
||||
<dt><?php esc_html_e( 'Subject', 'm365-login' ); ?></dt>
|
||||
<dd><?php echo esc_html( $cert_info['subject'] ); ?></dd>
|
||||
<dt><?php esc_html_e( 'Key size', 'm365-login' ); ?></dt>
|
||||
<dd><?php echo esc_html( $cert_info['bits'] ); ?> Bit RSA</dd>
|
||||
<dt><?php esc_html_e( 'Valid until', 'm365-login' ); ?></dt>
|
||||
<dd><?php echo esc_html( wp_date( get_option( 'date_format' ), $cert_info['not_after'] ) ); ?></dd>
|
||||
</dl>
|
||||
<div class="m365-cert__actions">
|
||||
<a class="button button-primary" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=' . self::POST_CERT ), self::POST_CERT ) ); ?>"><?php esc_html_e( 'Download certificate (.cer)', 'm365-login' ); ?></a>
|
||||
<button type="button" class="button" id="m365-cert-generate" data-replace="1"><?php esc_html_e( 'Generate new certificate', 'm365-login' ); ?></button>
|
||||
<label class="m365-check m365-check--inline">
|
||||
<input type="checkbox" name="<?php echo $field( 'cert_remove' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" value="1" id="m365-cert-remove" />
|
||||
<?php esc_html_e( 'Remove certificate when saving', 'm365-login' ); ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<p class="m365-cert__empty"><?php esc_html_e( 'No certificate stored yet.', 'm365-login' ); ?></p>
|
||||
<div class="m365-cert__actions">
|
||||
<button type="button" class="button button-primary" id="m365-cert-generate"><?php esc_html_e( 'Generate certificate', 'm365-login' ); ?></button>
|
||||
<span class="description"><?php esc_html_e( '3072-bit RSA, self-signed, valid for 2 years. The private key is stored encrypted and never shown or downloadable.', 'm365-login' ); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div id="m365-cert-result" class="m365-inline-result" hidden></div>
|
||||
<p class="description" style="margin-top:12px"><a href="#" id="m365-cert-paste-toggle"><?php esc_html_e( 'Use your own certificate instead (paste PEM)', 'm365-login' ); ?></a></p>
|
||||
<div id="m365-cert-paste" hidden>
|
||||
<div class="m365-field">
|
||||
<label for="m365-cert-key"><?php esc_html_e( 'Private key (PEM, unencrypted)', 'm365-login' ); ?></label>
|
||||
<textarea id="m365-cert-key" name="<?php echo $field( 'cert_key_pem' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" class="large-text m365-pem" rows="6" placeholder="-----BEGIN PRIVATE KEY-----" autocomplete="off" spellcheck="false"></textarea>
|
||||
</div>
|
||||
<div class="m365-field">
|
||||
<label for="m365-cert-cert"><?php esc_html_e( 'Certificate (PEM)', 'm365-login' ); ?></label>
|
||||
<textarea id="m365-cert-cert" name="<?php echo $field( 'cert_cert_pem' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" class="large-text m365-pem" rows="6" placeholder="-----BEGIN CERTIFICATE-----" spellcheck="false"></textarea>
|
||||
<p class="description"><?php esc_html_e( 'RSA, at least 2048 bits. The pair is validated and the key is encrypted when you save. Both fields stay empty afterwards.', 'm365-login' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="m365-guide" <?php echo $cert_info ? 'open' : ''; ?>>
|
||||
<summary><?php esc_html_e( 'Step-by-step: register the certificate in Entra ID', 'm365-login' ); ?></summary>
|
||||
<div class="m365-guide__body">
|
||||
<ol>
|
||||
<li><?php esc_html_e( 'Click Generate certificate above (or paste your own). Then click Download certificate (.cer) – the file contains only the public part.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Open entra.microsoft.com → Identity → Applications → App registrations and open your app.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Choose Certificates & secrets in the left menu, then the tab Certificates, and click Upload certificate.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Select the downloaded .cer file, add a description such as "WordPress login" and click Add.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Compare the thumbprint Entra ID shows with the thumbprint above – they must match exactly.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Make sure Certificate is selected above and save this page. If a client secret was stored before, you may delete it in Entra ID now.', 'm365-login' ); ?></li>
|
||||
</ol>
|
||||
<p class="m365-guide__note"><?php esc_html_e( 'How it works: for every token request WordPress signs a short-lived JWT (client assertion) with the private key; Microsoft verifies it with the uploaded certificate. Nothing secret is ever transmitted.', 'm365-login' ); ?></p>
|
||||
<p class="m365-guide__warn"><?php esc_html_e( 'Before the certificate expires: generate a new one here, upload it to Entra ID (both may be registered at the same time), save, then remove the old one from Entra ID. Sign-ins keep working during the switch.', 'm365-login' ); ?></p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="m365-field">
|
||||
|
|
@ -580,13 +764,16 @@ class M365_Login_Admin {
|
|||
</div>
|
||||
|
||||
<div class="m365-card">
|
||||
<h2 class="m365-card__title"><?php esc_html_e( 'Setup in 5 steps', 'm365-login' ); ?></h2>
|
||||
<h2 class="m365-card__title"><?php esc_html_e( 'Setup guide: app registration', 'm365-login' ); ?></h2>
|
||||
<ol class="m365-steps">
|
||||
<li><?php esc_html_e( 'Open the Microsoft Entra admin center → App registrations → New registration.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Choose "Accounts in this organizational directory only", set the platform to Web and paste the redirect URI above.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Copy the Application (client) ID and Directory (tenant) ID from the overview page.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Under Certificates & secrets create a client secret and copy its value (not the ID).', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Under Token configuration add the optional claim "email" for ID tokens (recommended), then save this page.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Open entra.microsoft.com → Identity → Applications → App registrations → New registration.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Name: e.g. "WordPress login". Supported account types: "Accounts in this organizational directory only" (single tenant).', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Redirect URI: choose the platform Web and paste the URI shown above. Then click Register.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'On the Overview page copy the Application (client) ID and the Directory (tenant) ID into the Connection tab.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Authentication: leave "ID tokens" unchecked (the plugin uses the authorization code flow) and "Allow public client flows" on No.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Token configuration → Add optional claim → ID → tick "email" → Add. Confirm the API permission prompt.', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Pick the authentication method on the Connection tab and follow its step-by-step guide (client secret or certificate).', 'm365-login' ); ?></li>
|
||||
<li><?php esc_html_e( 'Optional: restrict who may use the app under Enterprise applications → your app → Properties → "Assignment required" = Yes, then assign users/groups.', 'm365-login' ); ?></li>
|
||||
</ol>
|
||||
<p class="description"><?php esc_html_e( 'Required API permission: openid, profile, email (delegated) – granted by default.', 'm365-login' ); ?></p>
|
||||
<p class="description"><?php esc_html_e( 'Optional, for group restrictions: application permissions GroupMember.Read.All and User.Read.All (Microsoft Graph) with admin consent.', 'm365-login' ); ?></p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue