auth
napt.auth.credentials
Microsoft Graph authentication for NAPT.
Every command that talks to Intune (napt upload, napt promote apply,
napt promote plan --reconcile/--check-drift) calls
get_access_token once. The token comes
from the first source that works:
Non-interactive (CI/CD): 1. EnvironmentCredential -- service principal via AZURE_CLIENT_ID, AZURE_TENANT_ID and either AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH.
Interactive (a person at a terminal):
2. The active tenant's session established earlier with
napt auth login. Tokens are cached by MSAL in an OS-encrypted store
(DPAPI on Windows, Keychain on macOS, libsecret on Linux) and
refreshed silently; the browser or Windows broker is only
opened by napt auth login itself, never by napt upload. Several
tenants can be signed in at once; napt auth login --tenant-id
switches the active one.
CI/CD through a login step:
3. AzureCliCredential -- an existing az login session signed in as a
service principal, which is what OIDC login steps such as GitHub
Actions azure/login leave behind. Recommended over client secrets
when the CI platform supports it: no secret to store or rotate.
Last in the chain so a developer's napt auth login always wins,
and a session signed in as a person is refused rather than used,
since its token belongs to the Azure CLI's own application.
napt auth login uses the authorization code flow with PKCE against a
loopback redirect, or -- on Windows, when the MSAL broker runtime is
installed -- the Web Account Manager (WAM) broker, which gives single
sign-on with accounts known to Windows, honors device-based Conditional
Access, and keeps refresh tokens device-bound. The broker needs an
interactive Windows session; scheduled tasks, services, and SSH sessions
should use a service principal or OIDC instead.
Requires a NAPT app registration in Microsoft Entra ID with the
DeviceManagementApps.ReadWrite.All and Group.Read.All Microsoft Graph
permissions (application permissions for CI/CD, delegated for interactive
use). See the authentication documentation for setup instructions.
AuthConfig
dataclass
One tenant's interactive sign-in settings.
Attributes:
| Name | Type | Description |
|---|---|---|
client_id |
str
|
Application (client) ID of the NAPT app registration in this tenant. |
tenant_id |
str
|
Directory (tenant) ID the sign-in is scoped to. |
username |
str | None
|
Account that signed in last (UPN), or |
domain |
str | None
|
The tenant's default verified domain (e.g. |
display_name |
str | None
|
The tenant's organization display name, looked up
alongside |
Source code in napt/auth/credentials.py
AuthStore
dataclass
Everything napt auth login remembers, keyed by tenant.
Attributes:
| Name | Type | Description |
|---|---|---|
active |
str | None
|
Tenant ID that commands use, or |
tenants |
dict[str, AuthConfig]
|
Known tenants by tenant ID. |
Source code in napt/auth/credentials.py
AuthStatus
dataclass
What napt auth status reports about the current credential.
Attributes:
| Name | Type | Description |
|---|---|---|
method |
str
|
Human-readable credential source, e.g. |
account |
str | None
|
Signed-in user (UPN) for delegated tokens, or the client ID
for application tokens. |
tenant_id |
str | None
|
Tenant the token was issued for, when decodable. |
client_id |
str | None
|
App registration the token was issued to, when decodable. |
expires_at |
datetime | None
|
Access token expiry, when decodable. |
permissions |
list[str]
|
Graph permissions carried by the token -- delegated
scopes ( |
missing |
list[str]
|
Required Graph permissions ( |
Source code in napt/auth/credentials.py
load_auth_store
Reads what previous napt auth login runs remembered.
Returns:
| Type | Description |
|---|---|
AuthStore
|
The saved store, empty when no login has been run yet. |
Raises:
| Type | Description |
|---|---|
ConfigError
|
If the file exists but is unreadable or malformed. |
Source code in napt/auth/credentials.py
resolve_auth_config
resolve_auth_config(
tenant_id: str | None = None, client_id: str | None = None
) -> AuthConfig | None
Determines the tenant and app registration for interactive sign-in.
The tenant is tenant_id or the active one from the last login;
tenant_id may also be the default domain of a remembered tenant
(contoso.com), which is mapped back to its ID. The client ID is
client_id or the one remembered for that tenant. The remembered
username carries over only when the client ID is unchanged, since a
different app registration means a different cached session.
AZURE_* environment variables are deliberately not consulted: they
describe a non-interactive credential, not which app a person signs in
to.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant_id
|
str | None
|
Explicit tenant ID or remembered domain (from
|
None
|
client_id
|
str | None
|
Explicit client ID (from |
None
|
Returns:
| Type | Description |
|---|---|
AuthConfig | None
|
The resolved config, or |
AuthConfig | None
|
unknown. |
Raises:
| Type | Description |
|---|---|
ConfigError
|
If a saved config file exists but is malformed. |
Source code in napt/auth/credentials.py
get_credential
Builds the service principal credential from environment variables.
Reads AZURE_CLIENT_ID, AZURE_TENANT_ID and either
AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH, and uses
the .default scope, suitable for application permissions.
Returns:
| Type | Description |
|---|---|
EnvironmentCredential
|
The environment-backed credential; acquiring a token from it fails |
EnvironmentCredential
|
with ClientAuthenticationError when the variables are not all set. |
Source code in napt/auth/credentials.py
msal_error
Summarizes a failed MSAL token result as code: first line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
dict[str, Any]
|
The dict MSAL returns when no access token was issued. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The error code followed by the first line of its description. |
Source code in napt/auth/credentials.py
remember_tenant
Records config in the auth store as the active tenant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
AuthConfig
|
Tenant and client settings to store; replaces any entry for the same tenant. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
Path of the auth store file that was written. |
Source code in napt/auth/credentials.py
login
login(
*,
tenant_id: str | None = None,
client_id: str | None = None,
use_broker: bool = True
) -> AuthStatus
Signs in to a tenant and makes it the active one.
If the tenant already has a usable cached session, it is reused silently
-- so napt auth login --tenant-id <id> switches between signed-in
tenants without a prompt. Otherwise the Windows broker (when the MSAL
broker runtime is installed and use_broker is true) or the system
browser opens, and the resulting account is stored in NAPT's
encrypted token cache. The tenant, client ID, and signed-in username are
remembered so later logins need no arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant_id
|
str | None
|
Tenant ID; defaults to the active tenant. |
None
|
client_id
|
str | None
|
App registration client ID; overrides the one remembered for the tenant. |
None
|
use_broker
|
bool
|
Prefer the OS broker over a browser when available. |
True
|
Returns:
| Type | Description |
|---|---|
AuthStatus
|
Status of the token now in use, including any missing permissions. |
Raises:
| Type | Description |
|---|---|
AuthError
|
If no app registration is configured or the sign-in fails. |
ConfigError
|
If the saved auth config is malformed. |
Source code in napt/auth/credentials.py
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 | |
logout
Removes cached interactive sessions.
Signs the active tenant's account out of NAPT's token cache (and the OS
broker, when it was used). With all_tenants, every remembered tenant
is signed out. Client and tenant IDs are kept so the next
napt auth login needs no arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
all_tenants
|
bool
|
Sign out of every remembered tenant, not just the active one. |
False
|
Returns:
| Type | Description |
|---|---|
list[str]
|
Tenant IDs that had a session removed (empty if none was cached). |
Raises:
| Type | Description |
|---|---|
AuthError
|
If the OS-encrypted token cache cannot be opened. |
ConfigError
|
If the saved auth config is malformed. |
Source code in napt/auth/credentials.py
get_status
Reports which credential NAPT would use right now, or None.
Resolves a token exactly as
get_access_token does -- so
the answer reflects what napt upload will do -- and decodes it for
display.
Returns:
| Type | Description |
|---|---|
AuthStatus | None
|
The current credential's status, or |
AuthStatus | None
|
configured or signed in. |
Raises:
| Type | Description |
|---|---|
AuthError
|
If a credential is configured but fails (for example, a saved session that can no longer be refreshed). |
Source code in napt/auth/credentials.py
get_access_token
Acquires a Microsoft Graph access token.
Tries the non-interactive chain from
get_credential first, then the session
saved by napt auth login, then an existing Azure CLI (az login)
session. Never opens a browser: an interactive user who has not logged
in is told to run napt auth login.
Returns:
| Type | Description |
|---|---|
str
|
Bearer token string for use in Authorization headers. |
Raises:
| Type | Description |
|---|---|
AuthError
|
If no credential is available or the saved session can no longer be refreshed, with guidance on what to do. |
Example
Get a token and use it in a request:
Source code in napt/auth/credentials.py
napt.auth.registration
Entra ID app registration provisioning for napt auth setup.
Creates -- or brings up to spec -- the app registration that napt.auth.credentials signs in with, so an administrator never has to click through the portal:
- The application object with
http://localhostand the Windows broker redirect URI as a Mobile-and-desktop platform, and the Microsoft Graph permissions NAPT needs declared as both application permissions (CI/CD) and delegated permissions (interactive sign-in). - Its service principal, with tenant-wide admin consent for both kinds of permission.
- Optionally, a federated identity credential that lets a CI/CD platform's workflow obtain tokens through OIDC with no client secret. The issuer and subject come from the user; NAPT carries no platform-specific knowledge.
Every step is idempotent: an existing registration (found by display name
or --client-id) is patched with only what is missing, and rerunning on a
complete registration changes nothing.
The run is bootstrapped with a short-lived token from the Microsoft Graph
Command Line Tools first-party application -- the same one Connect-MgGraph
uses -- requested in the browser and held in memory only. It needs an
account holding at least the Application Administrator role. NAPT does not
store that account or its tokens; the browser may keep its own sign-in.
Redirect URIs and permissions are always written to the application object, never to the service principal, where a directory sync could drop them.
SetupSpec
dataclass
What napt auth setup should provision.
Attributes:
| Name | Type | Description |
|---|---|---|
tenant_id |
str
|
Directory (tenant) ID to provision in. |
display_name |
str
|
Display name of the app registration to find or create. |
client_id |
str | None
|
Existing registration to bring up to spec instead of matching by display name. |
federated_issuer |
str | None
|
OIDC issuer URL of the CI platform to trust (for
example GitHub Actions' |
federated_subject |
str | None
|
Subject claim the platform presents for the
workflow that may obtain tokens; its format is defined by the
platform (for GitHub Actions, |
federated_audience |
str
|
Audience claim; Entra's standard value is the default. |
federated_name |
str | None
|
Display name of the credential; derived from the subject when not given. |
adopt |
bool
|
Take over a registration matched by display name that NAPT
did not create (no provenance stamp). Not needed when
|
Source code in napt/auth/registration.py
federated_credential_name
property
Name of the federated credential: given, or derived from the subject.
__post_init__
Rejects a federated credential given only an issuer or only a subject.
Source code in napt/auth/registration.py
SetupResult
dataclass
What napt auth setup found or created.
Attributes:
| Name | Type | Description |
|---|---|---|
tenant_id |
str
|
Tenant the registration lives in. |
client_id |
str
|
Application (client) ID to use with NAPT. |
display_name |
str
|
The registration's display name. |
created |
bool
|
Whether the application object was created by this run. |
adopted |
bool
|
Whether this run took over a registration NAPT did not create. |
needs_adopt |
bool
|
The registration matched by name carries no NAPT stamp
and |
previous_spec |
int | None
|
Spec version the registration was stamped with before
this run, or |
changes |
list[str]
|
Human-readable list of what this run added; empty when the registration was already complete. |
Source code in napt/auth/registration.py
setup_app_registration
Creates or completes the NAPT app registration in a tenant.
Signs an administrator in, then finds or creates the application,
adds any missing redirect URIs and Graph permissions, ensures the
service principal exists with admin consent for every permission, and
adds the OIDC federated credential when requested. Finally records
the tenant and client ID as the active tenant for napt auth login.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
SetupSpec
|
What to provision. |
required |
Returns:
| Type | Description |
|---|---|
SetupResult
|
The registration's IDs and the list of changes made. |
Raises:
| Type | Description |
|---|---|
AuthError
|
If the administrator sign-in fails or lacks the rights to manage applications. |
ConfigError
|
If the display name is ambiguous, a given client ID does not exist, or Graph reports unexpected permission data. |
NetworkError
|
On Graph API failures. |
Example
Provision a tenant and trust a CI workflow through OIDC:
from napt.auth.registration import SetupSpec, setup_app_registration
result = setup_app_registration(
SetupSpec(
tenant_id="<tenant id>",
federated_issuer="https://token.actions.githubusercontent.com",
federated_subject="repo:contoso/intune-apps:ref:refs/heads/main",
)
)
print(result.client_id, result.changes)