Azure Active Directory authentication library for Elixir using Workload Identity Federation.
This library enables passwordless authentication to Azure services by exchanging external identity tokens (from GitHub Actions, AWS, Kubernetes, etc.) for Azure AD access tokens.
- π Passwordless Authentication - No secrets or certificates to manage
- π€ Auto-detection - Automatically detects the runtime environment
- π Multiple Providers - GitHub Actions, Kubernetes, AWS, and custom providers
- β‘ Token Caching - Built-in caching with automatic refresh
- π Multi-cloud - Supports Azure Public, Government, China, and Germany clouds
Add ex_azure_identity
to your list of dependencies in mix.exs
:
def deps do
[
{:ex_azure_identity, "~> 0.1.0"}
]
end
First, configure your Azure AD application to trust your external identity provider:
# Example for GitHub Actions
az ad app federated-credential create \
--id <APPLICATION_ID> \
--parameters '{
"name": "github-deploy",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:myorg/myrepo:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
# Auto-detect the environment (GitHub Actions, Kubernetes, etc.)
{:ok, credential} = ExAzureIdentity.Credentials.ClientAssertionCredential.new(
tenant_id: System.get_env("AZURE_TENANT_ID"),
client_id: System.get_env("AZURE_CLIENT_ID")
)
# Get an access token
{:ok, token} = ExAzureIdentity.get_token(credential, "https://graph.microsoft.com/.default")
# Use the token
headers = [{"Authorization", "Bearer #{token.access_token}"}]
Automatically uses GitHub's OIDC token when running in GitHub Actions:
# .github/workflows/deploy.yml
jobs:
deploy:
permissions:
id-token: write # Required for OIDC
steps:
- uses: actions/checkout@v3
- run: mix deps.get
- run: mix run my_azure_script.exs
Uses the projected service account token or standard service account token:
{:ok, credential} = ClientAssertionCredential.new(
tenant_id: "tenant-id",
client_id: "client-id",
token_provider: :kubernetes
)
Exchange AWS Cognito tokens for Azure AD tokens:
# Set the Cognito token in environment
System.put_env("AZURE_FEDERATED_TOKEN", cognito_token)
{:ok, credential} = ClientAssertionCredential.new(
tenant_id: "tenant-id",
client_id: "client-id",
token_provider: :aws
)
Use any OIDC-compliant identity provider:
# From environment variable
{:ok, credential} = ClientAssertionCredential.new(
tenant_id: "tenant-id",
client_id: "client-id",
token_provider: {:env, "MY_OIDC_TOKEN"}
)
# From file
{:ok, credential} = ClientAssertionCredential.new(
tenant_id: "tenant-id",
client_id: "client-id",
token_provider: {:file, "/path/to/token"}
)
# Custom function
{:ok, credential} = ClientAssertionCredential.new(
tenant_id: "tenant-id",
client_id: "client-id",
token_provider: {:callback, fn ->
# Your custom token acquisition logic
{:ok, get_my_token()}
end}
)
This library integrates seamlessly with ex_azure:
# Create a token provider
provider = ExAzureIdentity.create_token_provider(credential, "https://graph.microsoft.com/.default")
# Use with ex_azure
request = %ExAzure.Request{
auth: {:bearer_token, provider},
method: :get,
base_url: "https://graph.microsoft.com",
path: "/v1.0/users"
}
{:ok, users} = ExAzure.request(request)
Tokens are automatically cached to minimize API calls:
# First call fetches from Azure AD
{:ok, token1} = ExAzureIdentity.get_token(credential, scope)
# Subsequent calls use cached token (if still valid)
{:ok, token2} = ExAzureIdentity.get_token(credential, scope)
# Force refresh if needed
{:ok, new_token} = ExAzureIdentity.refresh_token(credential, scope)
Support for different Azure clouds:
{:ok, credential} = ClientAssertionCredential.new(
tenant_id: "tenant-id",
client_id: "client-id",
cloud: :government # :public (default), :government, :china, :germany
)
The library supports these environment variables:
-
GitHub Actions (auto-detected):
ACTIONS_ID_TOKEN_REQUEST_URL
ACTIONS_ID_TOKEN_REQUEST_TOKEN
AZURE_FEDERATED_TOKEN_AUDIENCE
(optional, defaults to "api://AzureADTokenExchange")
-
Kubernetes:
AZURE_FEDERATED_TOKEN_FILE
(optional, for projected tokens)
-
AWS/Generic:
AZURE_FEDERATED_TOKEN
(for pre-acquired tokens)
The library provides detailed error messages:
case ExAzureIdentity.get_token(credential, scope) do
{:ok, token} ->
# Use token
token.access_token
{:error, {:invalid_client, message}} ->
# Handle authentication error
Logger.error("Authentication failed: #{message}")
{:error, {:invalid_tenant, message}} ->
# Handle tenant configuration error
Logger.error("Tenant error: #{message}")
{:error, reason} ->
# Handle other errors
Logger.error("Failed to get token: #{inspect(reason)}")
end
- No Secrets: This library uses Workload Identity Federation, eliminating the need for client secrets or certificates
- Token Validation: Azure AD validates the external token's issuer, subject, and audience
- Scope Limitations: Always request the minimum required scope
- Token Expiry: Tokens are automatically refreshed before expiry (with a 5-minute buffer)
Ensure your environment has the necessary variables or files:
- GitHub Actions: Check that
id-token: write
permission is set - Kubernetes: Verify service account token is mounted
- AWS: Ensure
AZURE_FEDERATED_TOKEN
is set
Verify your Azure AD configuration:
az ad app show --id <CLIENT_ID>
az ad app federated-credential list --id <CLIENT_ID>
Check that the federated credential configuration matches your token:
- Issuer URL must match exactly (no trailing slashes)
- Subject must match the token's sub claim
- Audience should be "api://AzureADTokenExchange"
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.