I am playing to setup an Angular Single Page Application using MS MSAL Angular library to request an Access token to get user details from Microsoft Graph API and to authenticate into a custom WEB API build with DJango Python Framework. I use the django_auth_adfs and rest_framework libraries to build this API. I used the following tutorials to setup my environments :
- https://docs.microsoft.com/en-US/azure/active-directory/develop/tutorial-v2-angular-auth-code and https://azuread.github.io/microsoft-authentication-library-for-js/ref/msal-angular/ for the Angular Side
- https://django-auth-adfs.readthedocs.io/en/latest/rest_framework.html for the DJango side
In my Angular App, I am able to login with my Azure AD credential. I can see that the access tokens for MS Graph and for my API are well fetched but this last is not accepted by my WEB API.
On Azure AD, I setup the two applications. One for the Angular frontend and one for the django backend. Here are my configurations:
- For DJango backend:
- Authentication using Web configuration with redirect URI to http://localhost:8000/oauth2/callback and only organizational directory accounts (no implicit flow).
- Tried without and with a client secret (same result).
- No token configuration
- Permissions to MS Ggaph without admin consent required and granted for my organization as status.
- Scope defined to expose the API: api://xxxxxxxx-32d5-4175-9b05-xxxxxxxxxxxx/access_as_user (Admins and Users consent enabled) and my frontend added as Authorized client application.
- For Angular App:
- Authentication using Single-page application with redirect URI to http://localhost:4200/ and only organizational directory accounts.
- No secret
- No token configuration
- API permission to MS Graph User.Read and to my backend API (access as user). Both validated by admin.
Here are my django configurations in settings.py:
INSTALLED_APPS = [
...
"django_auth_adfs",
"rest_framework",
...
]
MIDDLEWARE = [
...
"django_auth_adfs.middleware.LoginRequiredMiddleware",
"corsheaders.middleware.CorsMiddleware",
]
AUTHENTICATION_BACKENDS = (
"django_auth_adfs.backend.AdfsAuthCodeBackend",
"django_auth_adfs.backend.AdfsAccessTokenBackend",
)
AUTH_ADFS = {
"TENANT_ID": AZURE_AD_TENANT_ID,
"CLIENT_ID": AZURE_CLIENT_ID,
"RELYING_PARTY_ID": AZURE_CLIENT_ID,
"AUDIENCE": AZURE_CLIENT_ID,
# "CLIENT_SECRET": AZURE_CLIENT_SECRET, # Tried with or without it
"CLAIM_MAPPING": {
"first_name": "given_name",
"last_name": "family_name",
"email": "upn",
},
"GROUPS_CLAIM": "roles",
"MIRROR_GROUPS": True,
"USERNAME_CLAIM": "upn",
"LOGIN_EXEMPT_URLS": [
"^api/v0/", # Prevent API from triggering a login redirect
],
}
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"django_auth_adfs.rest_framework.AdfsAccessTokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
],
"DEFAULT_PERMISSION_CLASSES": [
# Restrict API access for authenticated user by default
"rest_framework.permissions.IsAuthenticated",
],
}
Here are my angular app configurations: In app.module.ts:
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: environment.azure.client_app_id,
authority: environment.azure.authority,
redirectUri: environment.azure.redirectUri,
postLogoutRedirectUri: environment.azure.redirectUri,
navigateToLoginRequestUrl: true
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback: () => { },
logLevel: LogLevel.Info,
piiLoggingEnabled: false
}
}
});
}
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string> | null>();
protectedResourceMap.set('https://graph.microsoft.com/v1.0/me', ['user.read']);
protectedResourceMap.set('http://localhost:8000/api/v0/', ['api://xxxxxxxx-32d5-4175-9b05-xxxxxxxxxxxx/access_as_user']);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap,
};
}
export function MSALGuardConfigFactory(): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: [
'user.read',
'openid',
'profile',
'api://xxxxxxxx-32d5-4175-9b05-xxxxxxxxxxxx/access_as_user'
]
},
loginFailedRoute: "/"
};
}
I can see in my browser network spy that the access token requests are well done by the Angular MSAL library for both of the protected ressources (MS graph and my DJango API) but the one injected as Bearer when communicating with my API receive a 401 error.
Does anyone see what I am missing ? Thanks by advance for your help.
CodePudding user response:
I finally found the answer by myself ! In Django config, the AUDIENCE config was wrong:
"AUDIENCE": AZURE_CLIENT_ID,
must be "AUDIENCE": "api://" AZURE_CLIENT_ID,
With that correction, the access token is well validated.
Note that if you want to access the Django admin page by using your AD credentials, you have to add both versions of the audience as a list:
"AUDIENCE": ["api://" AZURE_CLIENT_ID, AZURE_CLIENT_ID],