Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/entra-live-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
env:
ENTRA_TENANT_ID: ${{ secrets.ENTRA_TENANT_ID }}
ENTRA_CLIENT_ID: ${{ secrets.ENTRA_CLIENT_ID }}
# Optional: only set when the app registration is a CONFIDENTIAL client (i.e. "Allow
# public client flows" is off). Absent, the check sends no secret, which is what a public
# client requires.
ENTRA_CLIENT_SECRET: ${{ secrets.ENTRA_CLIENT_SECRET }}
ENTRA_TEST_USERNAME: ${{ secrets.ENTRA_TEST_USERNAME }}
ENTRA_TEST_PASSWORD: ${{ secrets.ENTRA_TEST_PASSWORD }}
run: cargo run --example entra_live_check
31 changes: 23 additions & 8 deletions auth-oidc/examples/entra_live_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,26 @@ fn main() {

let token_endpoint = format!("https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token");
let http = reqwest::blocking::Client::new();
// A PUBLIC client sends no secret; a CONFIDENTIAL one must. Which of the two an app
// registration is depends on its "Allow public client flows" setting, and flipping that setting
// in the portal changes what this request has to look like — Entra rejects the mismatch with
// AADSTS7000218 rather than anything that names the setting. Sending the secret only when one
// is provisioned lets the same code cover both, so the check does not have to be edited every
// time the tenant is reconfigured.
let client_secret = env_or_skip("ENTRA_CLIENT_SECRET");
let mut form = vec![
("grant_type", "password"),
("client_id", client_id.as_str()),
("username", username.as_str()),
("password", password.as_str()),
("scope", "openid profile email"),
];
if let Some(secret) = client_secret.as_deref() {
form.push(("client_secret", secret));
}
let resp = http
.post(&token_endpoint)
.form(&[
("grant_type", "password"),
("client_id", &client_id),
("username", &username),
("password", &password),
("scope", "openid profile email"),
])
.form(&form)
.send()
.expect("real POST to Entra's real token endpoint");

Expand All @@ -61,7 +72,11 @@ fn main() {
status.is_success(),
"Entra rejected the real password grant ({status}): {body}. If this is \
AADSTS50055 (password expired), sign in interactively once as the test user to set a \
permanent password, then re-provision the ENTRA_TEST_PASSWORD secret."
permanent password, then re-provision the ENTRA_TEST_PASSWORD secret. If it is \
AADSTS7000218 ('the request body must contain client_assertion or client_secret'), the \
app registration is being treated as a CONFIDENTIAL client: either re-enable 'Allow \
public client flows' on it, or provision an ENTRA_CLIENT_SECRET repo secret, which this \
check will then send."
);
let id_token = body["id_token"]
.as_str()
Expand Down