Skip to content

himmelblau-idm/libhimmelblau

Repository files navigation

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 msal::PublicClientApplication;

Create an instance of the PublicClientApplication, then authenticate:

let authority = format!("https://login.microsoftonline.com/{}", tenant_id);
let app = PublicClientApplication::new(client_id, Some(&authority)).expect("Failed creating app");
let scope = vec![];
let token = app.acquire_token_by_username_password(username, password, scope).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(scope, &token.refresh_token).await?;

Or finally, you can perform a Device Authorization Grant:

let flow = app.initiate_device_flow(scope).await?;

// Prompt the user with the message found in flow.message

let token = app.acquire_token_by_device_flow(flow).await?;

If msal is built with the broker feature, you can enroll the device, then request an authentication token:

use kanidm_hsm_crypto::soft::SoftTpm;
use kanidm_hsm_crypto::{BoxedDynTpm, Tpm, AuthValue};

// First create the TPM object and a machine_key
let mut tpm = BoxedDynTpm::new(SoftTpm::new());
let auth_str = AuthValue::generate().expect("Failed to create hex pin");
let auth_value = AuthValue::from_str(&auth_str).expect("Unable to create auth value");
let loadable_machine_key = tpm
    .machine_key_create(&auth_value)
    .expect("Unable to create new machine key");
let machine_key = tpm
    .machine_key_load(&auth_value, &loadable_machine_key)
    .expect("Unable to load machine key");

let app = BrokerClientApplication::new(Some(&authority), None, None).expect("Failed creating app");

// 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(username, password).await?;
// Specify the attributes which will be used for enrollment
let attrs = match EnrollAttrs::new(
    domain.to_string(),
    Some("test_machine".to_string()), // Device name
    Some("Linux".to_string()), // Device type
    Some(0), // Join type
    Some("openSUSE Leap 15.5".to_string()), // OS version
) {
    Ok(attrs) => attrs,
    Err(e) => {
        println!("{:?}", e);
        return ();
    }
};
// Use the tpm for enrollment.
let (transport_key, cert_key, device_id) = app.enroll_device(&token.refresh_token, attrs, &mut tpm, &machine_key).await?;

// Request an authentication token
let token = app.acquire_token_by_username_password(username, password, scope, &mut tpm, &machine_key).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 = BoxedDynTpm::new(SoftTpm::new());
let loadable_machine_key = tpm
    .root_storage_key_create(&auth_value)
    .expect("Unable to create new machine key");
let machine_key = tpm
    .root_storage_key_load(&auth_value, &loadable_machine_key)
    .expect("Unable to load machine key");

let app = BrokerClientApplication::new(Some(&authority), Some(&transport_key), Some(&cert_key)).expect("Failed creating app");

Using the Python API

An 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:

  • email
  • 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/]:

uv tool install maturin
uv venv && uv pip install patchelf cffi
# build the library with python bindings and install it into the virtual environment
maturin build --features pyapi && uv pip install --force-reinstall target/wheels/libhimmelblau-*.whl

python example/msal_public_example.py

Sponsor this project

Contributors 7

Languages