Azure AD applications
Liens
- Azure AD token reference
- Help protect a web API by using bearer tokens from Azure AD
- Reporting options for Azure AD password management
Définitions
Client
Application ID Client ID |
ID de l'application cliente |
Redirect URI | URL de l'application pour une web app URL fictive pour un client natif https://wpfclient |
Tenant | Id de l'AD *.onmicrosoft.com |
Authority | https://login.microsoftonline.com/*.onmicrosoft.com |
Service Resource Id Resource Url |
App ID URI de l'application ADD serveur https://*.onmicrosoft.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
url du service pour les requêtes get |
Serveur
Application ID Client ID |
ID de l'application serveur |
Redirect URI PostLogoutRedirectUri |
URL du service |
App ID URI Audience |
https://*.onmicrosoft.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx https://*.onmicrosoft.com/MyAppName |
Tenant | Id de l'AD *.onmicrosoft.com |
AAD Instance | https://login.microsoftonline.com/{0} Utile pour former authority = aadInstance + tenantId |
Informations
Tenant (id) | Azure AD → Properties → Directory ID |
Tenant (url) Domain |
Mettre la sourie sur le nom de l'utilisateur en haut à droite → attendre le tooltip → Domain |
Debug
- View → Server Explorer
- Server Explorer → Azure → App Service → MyApp → MyApp → cliqu-droit → Attach Debugger
Détail des erreurs
Web.config |
<!-- Affiche le détail des erreurs --> <customErrors mode="Off" /> |
Permissions
- Modifier les permissions de l'applications
- Appliquer les permissions avec le bouton Grant Access
Erreurs possibles:
- Authorization_RequestDenied - Insufficient privileges to complete the operation
Liens:
Application Permissions vs Delegated Permissions
- Application Permissions: appel de l'application elle-même
- Delegated Permissions: appel avec l'utilisateur connecté
Multi-tenant
Azure Active Directory → App registrations → MyApp → Settings → Properties → Multi-tenanted = Yes
App_Start\Startup.Auth.cs |
public void ConfigureAuth(IAppBuilder app) { app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { // ... TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true, // turns off the default Issuer validation ValidateIssuer = false }, Notifications = new OpenIdConnectAuthenticationNotifications { // ... // assigns to the Redirect_Uri and Post_Logout_Redirect_Uri RedirectToIdentityProvider = (context) => { string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase; context.ProtocolMessage.RedirectUri = appBaseUrl; context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl; return Task.FromResult(0); } } |
Erreurs
AADSTS70001: Application with identifier 'xxx' was not found in the directory yyy
Redéfinir le RedirectToIdentityProvider.
AADSTS50011: The reply address 'http://myapp.azurewebsites.net' does not match the reply addresses configured for the application: 'xxx'.
La reply addess est en http alors que celle enregistrée est en https.
Forcer la webapp en https
AADSTS90094: The grant requires admin permission.
Se connecter avec un compte admin pour donner l'autorisation d'accès à tous les utilisateurs du tenant. lien
Se désabonner d'un directory
En cliquant sur le nom d'utilisateur en haut à droite il est possible de passer d'un directory à un autre.
Pour se désabonner d'un directory, il faut supprimer l'utilisateur dans ce directory.
Connect to Office 365 PowerShell
Import-Module MSOnline Connect-MsolService Get-MsolUser -UserPrincipalName 'me@domain.fr' | Select LastPasswordChangeTimestamp |
- How to get password policy for Azure Active Directory logged in user
- Azure Active Directory PowerShell for Graph
Installation:
- Microsoft Online Services Sign-In Assistant for IT Professionals RTW
- Azure Active Directory Module for Windows PowerShell
Application web ASP.NET MVC avec une authentification Azure AD App
Javascript SPA calling Azure AD Secured Web API
Limitation: le client javascript ne peut pas stocker de manière sécurisé les refresh tokens (pas de bon chiffrement pour les web app). Une fois le client fermé, il faut s'authentifier à nouveau. |
Service WebAPI
Packages NuGet:
- Microsoft.Owin.Cors
- Microsoft.Owin.Host.SystemWeb
- Microsoft.Owin.Security.ActiveDirectory
App_Start/Startup.cs |
// nécessite Microsoft.Owin.Host.SystemWeb [assembly: OwinStartup(typeof(AzureAdLocationService.App_Start.Startup))] namespace AzureAdLocationService.App_Start { public class Startup { public void Configuration(IAppBuilder app) { // Cross Origin Resource Sharing - CORS // Par défault l'appel depuis un doamine vers un autre n'est pas autorisé. app.UseCors(CorsOptions.AllowAll); // Authentication var azureAdBearerAuthOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions { // Azure AD Tenant Tenant = ConfigurationManager.AppSettings["ida:Tenant"] }; azureAdBearerAuthOptions.TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters { // Audience = App ID URI // http://mydomain.onmicrosoft.com/MyApp ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] }; app.UseWindowsAzureActiveDirectoryBearerAuthentication(azureAdBearerAuthOptions); |
Web.config |
<configuration> <appSettings> <add key="ida:Tenant" value="mydomain.onmicrosoft.com" /> <add key="ida:Audience" value="https://mydomain.onmicrosoft.com/MyApp" /> |
Autoriser OAuth2 implicite flow:
Azure → Azure Active Directory → App registration → MyApp → Manifest
"oauth2AllowImplicitFlow": true |
Client Javascript SPA
- Azure → Azure Active Directory → App registration → New application registration
- Application type: Native
- Redirect URI: https://<SSL URL>
- Récupérer l'Application ID (= Client ID) et la reporter dans Web.config
- Autoriser OAuth2 implicite flow: Azure → Azure Active Directory → App registration → MyApp → Manifest
"oauth2AllowImplicitFlow": true
- Autoriser le client à accéder au service:
- Required Permissions → Add → Select an API → MyServiceApp → Select → Access MyServiceApp → Save
Utiliser azure-activedirectory-library-for-js Télécharger adal.js et adal-angular.js |
Scripts/app.js |
(function () { "use strict"; var locationApp = angular.module('locationApp', ['AdalAngular']); locationApp.config(['$httpProvider', 'adalAuthenticationServiceProvider', '$locationProvider', function ($httpProvider, adalProvider, $locationProvider) { // When HTML5 mode is configured, ensure the $locationProvider hashPrefix is set $locationProvider.html5Mode(true).hashPrefix('!'); var endpoints = { "https://localhost:port": "https://mydomain.onmicrosoft.com/MyServerApp" } adalProvider.init( { instance: 'https://login.microsoftonline.com/', tenant: 'mydomain.onmicrosoft.com', clientId: '...', //localLoginUrl: "/login", // optional //redirectUri : "your site", optional endpoints: endpoints }, $httpProvider); }]); var locationController = locationApp.controller("locationController", [ '$scope', '$http', 'adalAuthenticationService', function ($scope, $http, adalService) { $scope.getData = function () { $http.get("https://localhost:port/api/data?param=value") .then(function (response) { // success $scope.data = response.data; }, function (error) { // failure $scope.error = error.data; }); } $scope.login = function () { adalService.login(); } $scope.logout = function () { adalService.logOut(); } }]); })(); |
index.html |
<!DOCTYPE html> <html> <head> <!-- évite l'erreur $location in HTML5 mode requires a <base> tag to be present! --> <base href="/"> <meta charset="utf-8" /> <title></title> <script src="Scripts/angular.js"></script> <script src="Scripts/adal.js"></script> <script src="Scripts/adal-angular.js"></script> <script src="Scripts/app.js"></script> </head> |
Native Application Calling Azure AD Secured Web API
Les Native Applications peuvent stocker et utiliser les refresh tokens. |
Nuget:
- Microsoft.IdentityModel.Clients.ActiveDirectory
Références:
- System.Net.Http
- System.Web.Extensions
- System.Security
MainWindows.xaml.cs |
private static string aadInstance = "https://login.microsoftonline.com/{0}"; private static string tenant = "xxx.onmicrosoft.com"; private static string clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; Uri redirectUri = new Uri("https://wpfclient"); private static string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant); private AuthenticationContext authContext = null; private AuthenticationResult authResult = null; private static string serviceResourceId = "https://xxx.onmicrosoft.com/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"; private HttpClient httpClient = new HttpClient(); private static string serviceBaseAddress = "https://localhost:ppppp/"; public MainWindow() { InitializeComponent(); authContext = new AuthenticationContext(authority, new FileCache()); } private async void CallService_Click(object sender, RoutedEventArgs e) { if (authResult == null) { ServiceResult.Text = "You need to sign-in first !!!"; } try { authResult = await authContext.AcquireTokenAsync(serviceResourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Never)); } catch (AdalException aex) { ServiceResult.Text = aex.ToString(); return; } httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken); var response = await httpClient.GetAsync(serviceBaseAddress + "api/xxx?arg=vvv"); if (response.IsSuccessStatusCode) { var s = await response.Content.ReadAsStringAsync(); ServiceResult.Text = s; } else { ServiceResult.Text = "An error occured: " + response.ReasonPhrase; } } private async void SignIn_Click(object sender, RoutedEventArgs e) { try { authResult = await authContext.AcquireTokenAsync(serviceResourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Always)); ServiceResult.Text = "You signed in!"; } catch (AdalException aex) { ServiceResult.Text = aex.ToString(); } } private void SignOut_Click(object sender, RoutedEventArgs e) { authContext.TokenCache.Clear(); ClearCookies(); ServiceResult.Text = "You signed out!"; } private void ClearCookies() { const int INTERNET_OPTION_END_BROWSER_SESSION = 42; InternetSetOption(IntPtr.Zero, INTERNET_OPTION_END_BROWSER_SESSION, IntPtr.Zero, 0); } [DllImport("wininet.dll", SetLastError = true)] private static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int lpdwBufferLength); |
FileCache.cs |
class FileCache : TokenCache { private string _cacheFilePath; private static readonly object _fileLock = new object(); public FileCache(string filePath = @".\TokenCache.dat") { _cacheFilePath = filePath; this.AfterAccess = AfterAccessNotification; this.BeforeAccess = BeforeAccessNotification; lock (_fileLock) { byte[] data = null; if (File.Exists(_cacheFilePath)) { data = ProtectedData.Unprotect(File.ReadAllBytes(_cacheFilePath), null, DataProtectionScope.CurrentUser); } this.Deserialize(data); } } public override void Clear() { base.Clear(); File.Delete(_cacheFilePath); } void BeforeAccessNotification(TokenCacheNotificationArgs args) { lock (_fileLock) { byte[] data = null; if (File.Exists(_cacheFilePath)) { data = ProtectedData.Unprotect(File.ReadAllBytes(_cacheFilePath), null, DataProtectionScope.CurrentUser); } this.Deserialize(data); } } void AfterAccessNotification(TokenCacheNotificationArgs args) { if (this.HasStateChanged) { lock (_fileLock) { File.WriteAllBytes(_cacheFilePath, ProtectedData.Protect(this.Serialize(), null, DataProtectionScope.CurrentUser)); this.HasStateChanged = false; } } } } |
Web site calling Azure AD Secured Web API
Azure AD Secured Web API calling Graph API on behalf of a Native Application
- L'applicative native se logue et obtient un token AAD
- Elle utilise ce token dans le header de ses requêtes pour faire des appels à l'API web
- L'API web récupère le token AAD de l'application native dans le BootstrapContext
- Elle utilise le token AAD de l'application native, son id AAD, la clé secrète AAD pour générer ses propres tokens
- Nuget → Microsoft.IdentityModel.Clients.ActiveDirectory
- Assembly → System.IdentityModel
string accessToken = null; var bootstrapContextObject = ClaimsPrincipal.Current.Identities.First().BootstrapContext; if (bootstrapContextObject is BootstrapContext bootstrapContext) { accessToken = bootstrapContext.Token; } else if (bootstrapContextObject is string bootstrapContextString) { accessToken = bootstrapContextString; } else { } var authenticationContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority); var clientCredential = new ClientCredential(AadAppId, AadAppSecretKey); string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value; var userAssertion = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName); // génération d'un nouveau token pour l'API web qui permettra de faire des requêtes Graph API AuthenticationResult result = await authenticationContext.AcquireTokenAsync(audience, clientCredential, userAssertion); string newFreshToken = result.AccessToken; |
App_Start\Startup.cs |
public class Startup { public void Configuration(IAppBuilder app) { app.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions { Tenant = ConfigurationManager.AppSettings["ida:Tenant"], TokenValidationParameters = new TokenValidationParameters { // forcer SaveSigninToken pour avoir le BootstrapContext.Token SaveSigninToken = true, ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] } }); |
Web.config |
<configuration> <configSections> <!--WIF 4.5 sections --> <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> </configSections> <system.identityModel> <identityConfiguration saveBootstrapContext="true" /> </system.identityModel> |
BootstrapContext null
- Get bootStrapContext token from ClaimsPrincipal in MVC application
- Azure Active Directory B2C: Build an ASP.NET Core MVC web API
Token
Type | Description | Durée de vie |
---|---|---|
ID / Access Token | authentifie les utilisateurs et les requêtes. Fournit par Azure AD et utilisé seulement par l'application cliente. Après une authentification valide, Azure AD retourne un Access Token JWT encodé en base64 |
1h |
Refresh Token | demande de nouveaux ID / Access Token sans interaction avec l'utilisateur. Fournit et utilisé seulement par Azure AD. | 14 jours |
Erreurs
Application is requesting a token for itself
This scenario is supported only if resource is specified using the GUID based App Identifier.
L'application ne peut demander un token pour elle-même, sauf si elle utilise son App ID au lieu de son App ID URI / Audience.
audience = "https://xxx.onmicrosoft.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"; // remplacer l'audience par l'App ID audience = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyy"; AuthenticationResult result = await authenticationContext.AcquireTokenAsync(audience, clientCredential, userAssertion); |
Could not load type Tokens.TokenValidationParameters from assembly Tokens.Jwt
Could not load type 'System.IdentityModel.Tokens.TokenValidationParameters' from assembly 'System.IdentityModel.Tokens.Jwt, Version=5.1.5.0
System.IdentityModel.Tokens.Jwt 5.x is not compatible with Katana 3.
It is for .NET Core. For .NET Framework use version 4.x
IAppBuilder does not contain a definition for SetDefaultSignInAsAuthenticationType
# ajouter le using using Microsoft.Owin.Security; |
Authorization has been denied for this request
Vérifier les paramètres clientId, tenant, authority, resourceUrl
You can't access this application
MyApp needs permission to access resources in your organization that only an admin can grant. Please ask an admin to grant permission to this app before you can use it.
L'utilisateur doit autoriser (Azure AD consent framework) l'accès à MyApp. Ici il n'a pas assez de droits pour le faire.
- Solution 1: autoriser par défaut l'accès à MyApp pour tous les utilisateurs.
- Solution 2: donner les droits aux utilisateur d'autoriser leur accès à MyApp.
- AAD → User Settings → Enterprise applications → Users can consent to apps accessing company data on their behalf → Yes
- Solution 3: désactiver la demande d'autorisation à MyApp.