MSAL
The purpose of this project is to implement MSAL for Rust, based on the specifications found in the Microsoft API Reference for ClientApplication Class and PublicClientApplication Class. These are Python references which will be mimicked in Rust here.
The project also implements the [MS-DRS] protocol, which is undocumented by microsoft. A protocol specification is in progress as part of the himmelblau project.
In addition to the ClientApplication Class and [MS-DRS] implementations, this project implements [MS-OAPXBC] sections 3.1.5.1.2 Request for Primary Refresh Token and 3.1.5.1.3 Exchange Primary Refresh Token for Access Token. These are not implemented in Microsoft's MSAL libraries, but are possible when authenticating from an enrolled device.
How do I use this library?
Import the module into your project, then include the PublicClientApplication:
use PublicClientApplication;
Create an instance of the PublicClientApplication, then authenticate:
let authority = format!;
let app = new.expect;
let scope = vec!;
let token = app.acquire_token_by_username_password.await?;
You can obtain your client_id and tenant_id from the Azure portal.
You can perform a silent auth using a previously obtained refresh token:
let token = app.acquire_token_silent.await?;
Or finally, you can perform a Device Authorization Grant:
let flow = app.initiate_device_flow.await?;
// Prompt the user with the message found in flow.message
let token = app.acquire_token_by_device_flow.await?;
If msal is built with the broker feature, you can enroll the device, then request an authentication token:
use SoftTpm;
use ;
// First create the TPM object and a machine_key
let mut tpm = new;
let auth_str = generate.expect;
let auth_value = from_str.expect;
let loadable_machine_key = tpm
.machine_key_create
.expect;
let machine_key = tpm
.machine_key_load
.expect;
let app = new.expect;
// Obtain a token for authentication. If authenticating here without MFA, the PRT and
// user token will not have the mfa claim. Use initiate_device_flow_for_device_enrollment()
// and acquire_token_by_device_flow() to authenticate with the
// mfa claim.
let token = app.acquire_token_by_username_password_for_device_enrollment.await?;
// Specify the attributes which will be used for enrollment
let attrs = match new ;
// Use the tpm for enrollment.
let = app.enroll_device.await?;
// Request an authentication token
let token = app.acquire_token_by_username_password.await?;
In order to initialize a BrokerClientApplication that was previously enrolled, ensure you've cached your auth_value, loadable_machine_key, transport_key, and cert_key. The auth_value MUST be stored in a secure manor only accessible to your application. Preferably your application should execute as a unique user, and only that user will have read access to the auth_value. Re-initialize as follows:
let mut tpm = new;
let loadable_machine_key = tpm
.root_storage_key_create
.expect;
let machine_key = tpm
.root_storage_key_load
.expect;
let app = new.expect;
Entra OpenSSH certificates
BrokerClientApplication can request the same Microsoft-issued OpenSSH user
certificate used by Azure SSH login. The caller generates and retains an
ephemeral RSA private key, then passes its ssh-rsa public-key line to
exchange_prt_for_ssh_certificate. The returned access_token is treated as a
base64-encoded OpenSSH certificate, not as a JWT. Use
EntraSshCertificate::openssh_certificate() to obtain the complete
ssh-rsa-cert-v01@openssh.com line suitable for an OpenSSH certificate file.
exchange_prt_for_ssh_certificate deliberately uses the sealed PRT flow; it
does not expose a refresh-token or interactive fallback. The result also
provides the complete signing CA OpenSSH public key and its SHA-256 fingerprint
for target-side trust decisions.
C callers own the returned certificate until ssh_certificate_free, and must
release strings returned by its accessors with string_free. Principal arrays
must be released with ssh_certificate_free_principals and their exact count.
Python values are managed by Python.
The library does not write key files, invoke ssh, or validate target-side CA
trust. Callers remain responsible for secure private-key storage and cleanup.
Using the On-Behalf-Of (OBO) flow
The OBO flow is available when built with the on_behalf_of feature.
Rust (confidential middle-tier service):
use ;
let authority = format!;
let credential = from_secret;
let app = new?;
match app
.acquire_token_on_behalf_of
.await
C API:
- Initialize with
confidential_client_init_with_secret. Note: only client secret credentials are currently supported in the C API; certificate-based credentials are available from Rust only. - Exchange the incoming user token with
confidential_acquire_token_on_behalf_of. - Compile OBO C callers with
-DON_BEHALF_OFso OBO declarations are visible in the generated header. - Read token fields with:
obo_token_access_tokenobo_token_token_typeobo_token_expires_inobo_token_ext_expires_inobo_token_scope(returnsNULLwhen not present)obo_token_refresh_token(returnsNULLwhen not present)
- On Conditional Access claims challenge, check
MSAL_ERROR.code == OBO_INTERACTION_REQUIREDand readMSAL_ERROR.claims.
Python API:
- Create
ConfidentialClientApplication(client_id, authority, client_secret). Note: only client secret credentials are currently supported in the Python API; certificate-based credentials are available from Rust only. - Call
acquire_token_on_behalf_of(user_assertion, scopes). - Catch
OboInteractionRequiredErrorand inspect:claimserrorerror_descriptionerror_codessuberror
Reference examples:
example/msal_obo_example.cexample/msal_obo_example.pyexample/msal_obo_end_to_end_test.py(full functional validation: upstream token -> OBO -> Graph/me)
Using the Python API
A script that uses PublicClientApplication from Python can be found in the examples.
This script uses a public client created via an Azure App Registration. The URL https://login.microsoftonline.com/common/oauth2/nativeclient should be used as a "Mobile and desktop applications" Redirect URI under the Authentication settings for the App Registration. The script was tested with the following permissions for the application:
- Group.Read.All
- offline_access
- openid
- profile
- User.Read
The script needs the tenant ID and client ID for the App Registration, and allows logging in with a username and password. It also demonstrates explicitly choosing an MFA method for the login, rather than using the default MFA method.
To build libhimmelblau and test it with this script using (uv)[https://docs.astral.sh/uv/]:
&&
# build the library with python bindings and install it into the virtual environment
&&