Multi-Cloud Manager uses Microsoft Authentication Library (MSAL) for Python to authenticate users and access Azure resources through the Azure Management API.
Authentication Flow
The Azure authentication module implements the OAuth 2.0 authorization code flow:
Environment Variables
Configure these environment variables in your .env file:
# Azure OAuth Configuration
AZURE_TENANT_ID=your-tenant-id
AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET=your-client-secret
# Application Configuration
APP_BASE_URL=http://localhost:5000
Variable Descriptions
| Variable | Description | Example |
|---|
AZURE_TENANT_ID | Your Azure AD tenant ID | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee |
AZURE_CLIENT_ID | Application (client) ID from app registration | 11111111-2222-3333-4444-555555555555 |
AZURE_CLIENT_SECRET | Client secret value (not the secret ID) | abc123~xyz... |
APP_BASE_URL | Base URL where your backend is hosted | https://api.yourdomain.com |
Keep your AZURE_CLIENT_SECRET secure and never commit it to version control. Rotate secrets regularly.
Azure App Registration
Create App Registration
Navigate to Azure Portal → Azure Active Directory → App registrations → New registration
- Name: Multi-Cloud Manager
- Supported account types: Single tenant or multi-tenant based on your needs
- Redirect URI:
http://localhost:5000/getAToken (for development)
Create Client Secret
In your app registration:
- Go to Certificates & secrets → New client secret
- Add a description and set expiration
- Copy the secret value (not the ID) immediately
Configure API Permissions
Add the following API permission:
- API: Azure Service Management
- Permission:
user_impersonation (delegated)
- Grant admin consent if required
Note Configuration Values
Copy these values from the Overview page:
- Application (client) ID →
AZURE_CLIENT_ID
- Directory (tenant) ID →
AZURE_TENANT_ID
Code Implementation
The Azure authentication module is located in backend/auth/azure_auth.py:
MSAL Configuration
import msal
import os
from azure.identity import ClientSecretCredential
from azure.mgmt.subscription import SubscriptionClient
AUTHORITY = f"https://login.microsoftonline.com/{os.getenv('AZURE_TENANT_ID')}"
SCOPE = ["https://management.azure.com/.default"]
CLIENT_ID = os.getenv("AZURE_CLIENT_ID")
CLIENT_SECRET = os.getenv("AZURE_CLIENT_SECRET")
APP_BASE_URL = os.getenv("APP_BASE_URL", "http://localhost:5000")
REDIRECT_PATH = "/getAToken"
def build_msal_app():
return msal.ConfidentialClientApplication(
CLIENT_ID,
authority=AUTHORITY,
client_credential=CLIENT_SECRET
)
OAuth Scopes
The application requests the following scope:
SCOPE = ["https://management.azure.com/.default"]
This scope grants access to Azure Resource Manager APIs, allowing the application to:
- List subscriptions
- Query resource groups
- Manage virtual machines and other resources
API Endpoints
Login Endpoint
Route: /api/login/azure
Method: GET
Description: Initiates the Azure OAuth flow by redirecting to Microsoft’s authorization endpoint.
@azure_auth.route("/api/login/azure")
def login():
url = build_msal_app().get_authorization_request_url(
scopes=SCOPE,
redirect_uri=f"{APP_BASE_URL}{REDIRECT_PATH}",
)
return redirect(url)
Usage:
curl -L http://localhost:5000/api/login/azure
Callback Endpoint
Route: /getAToken
Method: GET
Description: Handles the OAuth callback, exchanges the authorization code for tokens, and stores account information in the session.
@azure_auth.route(REDIRECT_PATH)
def authorized():
code = request.args.get("code")
if not code:
return jsonify({"error": "Brak kodu"}), 401
result = build_msal_app().acquire_token_by_authorization_code(
code, scopes=SCOPE, redirect_uri=f"{APP_BASE_URL}{REDIRECT_PATH}"
)
if "id_token_claims" in result:
# Store user and token in session
session["user"] = result["id_token_claims"]
session["access_token"] = result.get("access_token")
# Fetch Azure subscriptions
cred = ClientSecretCredential(
tenant_id=os.getenv("AZURE_TENANT_ID"),
client_id=os.getenv("AZURE_CLIENT_ID"),
client_secret=os.getenv("AZURE_CLIENT_SECRET")
)
sub_client = SubscriptionClient(cred)
subs = [s.subscription_id for s in sub_client.subscriptions.list()]
# Store account in session
azure_account = {
"provider": "azure",
"tenantId": os.getenv("AZURE_TENANT_ID"),
"displayName": session["user"].get("name") or "Azure account",
"subscriptions": subs
}
# Update or add account
accounts = session.get("accounts", [])
# Remove existing Azure account with same tenantId
accounts = [acc for acc in accounts
if not (acc.get("provider") == "azure" and
acc.get("tenantId") == azure_account["tenantId"])]
accounts.append(azure_account)
session["accounts"] = accounts
return redirect("http://localhost:3000/dashboard")
Session Data Structure
After successful authentication, the session contains:
session = {
"user": {
"name": "John Doe",
"preferred_username": "[email protected]",
"oid": "user-object-id",
# ... other ID token claims
},
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"accounts": [
{
"provider": "azure",
"tenantId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"displayName": "John Doe",
"subscriptions": [
"sub-id-1",
"sub-id-2"
]
}
]
}
Subscription Discovery
After authentication, the module automatically discovers Azure subscriptions:
from azure.identity import ClientSecretCredential
from azure.mgmt.subscription import SubscriptionClient
cred = ClientSecretCredential(
tenant_id=tenant_id,
client_id=client_id,
client_secret=client_secret
)
sub_client = SubscriptionClient(cred)
try:
subs = [s.subscription_id for s in sub_client.subscriptions.list()]
except Exception as e:
print(f"Error listing subscriptions: {e}")
subs = []
If subscription listing fails, the account is still added with an empty subscription list.
Account Deduplication
The system prevents duplicate Azure accounts in the session by comparing tenantId:
# Filter out existing Azure accounts with the same tenantId
filtered_accounts = [
acc for acc in current_accounts
if not (acc.get("provider") == "azure" and acc.get("tenantId") == tenant_id)
]
filtered_accounts.append(azure_account)
session["accounts"] = filtered_accounts
Troubleshooting
Error: “Brak kodu”
Cause: No authorization code in callback URL
Solution: Check that the redirect URI in Azure App Registration matches {APP_BASE_URL}/getAToken
Error: Failed to List Subscriptions
Cause: Insufficient permissions or invalid credentials
Solution:
- Verify the client secret is correct and not expired
- Ensure the user has permissions to view subscriptions
- Check that API permissions include Azure Service Management
Error: Invalid Client
Cause: Client ID or client secret mismatch
Solution: Double-check AZURE_CLIENT_ID and AZURE_CLIENT_SECRET environment variables
Security Best Practices
- Always use HTTPS in production for
APP_BASE_URL
- Configure Flask session with a strong
SECRET_KEY
- Rotate client secrets before expiration
- Use Azure Key Vault for secret management in production
- Enable Conditional Access policies for additional security
Next Steps