AzureADAuthenticationBuilderExtensionsTests.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using Microsoft.AspNetCore.Authorization;
  3. using System;
  4. using Microsoft.AspNetCore.Authentication.AzureAD.UI;
  5. using Microsoft.AspNetCore.Authentication.Cookies;
  6. using Microsoft.AspNetCore.Authentication.JwtBearer;
  7. using Microsoft.AspNetCore.Authentication.OpenIdConnect;
  8. using Microsoft.AspNetCore.Http;
  9. using Microsoft.Extensions.DependencyInjection;
  10. using Microsoft.Extensions.Logging;
  11. using Microsoft.Extensions.Logging.Abstractions;
  12. using Microsoft.Extensions.Options;
  13. using Xunit;
  14. namespace Microsoft.AspNetCore.Authentication
  15. {
  16. public class AzureADAuthenticationBuilderExtensionsTests
  17. {
  18. [Fact]
  19. public void AddAzureAD_AddsAllAuthenticationHandlers()
  20. {
  21. // Arrange
  22. var services = new ServiceCollection();
  23. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  24. // Act
  25. services.AddAuthentication()
  26. .AddAzureAD(o => { });
  27. var provider = services.BuildServiceProvider();
  28. // Assert
  29. Assert.NotNull(provider.GetService<OpenIdConnectHandler>());
  30. Assert.NotNull(provider.GetService<CookieAuthenticationHandler>());
  31. Assert.NotNull(provider.GetService<PolicySchemeHandler>());
  32. }
  33. [Fact]
  34. public void AddAzureAD_ConfiguresAllOptions()
  35. {
  36. // Arrange
  37. var services = new ServiceCollection();
  38. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  39. // Act
  40. services.AddAuthentication()
  41. .AddAzureAD(o =>
  42. {
  43. o.Instance = "https://login.microsoftonline.com";
  44. o.ClientId = "ClientId";
  45. o.ClientSecret = "ClientSecret";
  46. o.CallbackPath = "/signin-oidc";
  47. o.Domain = "domain.onmicrosoft.com";
  48. o.TenantId = "Common";
  49. });
  50. var provider = services.BuildServiceProvider();
  51. // Assert
  52. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  53. Assert.NotNull(azureADOptionsMonitor);
  54. var azureADOptions = azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme);
  55. Assert.Equal(AzureADDefaults.OpenIdScheme, azureADOptions.OpenIdConnectSchemeName);
  56. Assert.Equal(AzureADDefaults.CookieScheme, azureADOptions.CookieSchemeName);
  57. Assert.Equal("https://login.microsoftonline.com", azureADOptions.Instance);
  58. Assert.Equal("ClientId", azureADOptions.ClientId);
  59. Assert.Equal("ClientSecret", azureADOptions.ClientSecret);
  60. Assert.Equal("/signin-oidc", azureADOptions.CallbackPath);
  61. Assert.Equal("domain.onmicrosoft.com", azureADOptions.Domain);
  62. var openIdOptionsMonitor = provider.GetService<IOptionsMonitor<OpenIdConnectOptions>>();
  63. Assert.NotNull(openIdOptionsMonitor);
  64. var openIdOptions = openIdOptionsMonitor.Get(AzureADDefaults.OpenIdScheme);
  65. Assert.Equal("ClientId", openIdOptions.ClientId);
  66. Assert.Equal($"https://login.microsoftonline.com/Common", openIdOptions.Authority);
  67. Assert.True(openIdOptions.UseTokenLifetime);
  68. Assert.Equal("/signin-oidc", openIdOptions.CallbackPath);
  69. Assert.Equal(AzureADDefaults.CookieScheme, openIdOptions.SignInScheme);
  70. var cookieAuthenticationOptionsMonitor = provider.GetService<IOptionsMonitor<CookieAuthenticationOptions>>();
  71. Assert.NotNull(cookieAuthenticationOptionsMonitor);
  72. var cookieAuthenticationOptions = cookieAuthenticationOptionsMonitor.Get(AzureADDefaults.CookieScheme);
  73. Assert.Equal("/AzureAD/Account/SignIn/AzureAD", cookieAuthenticationOptions.LoginPath);
  74. Assert.Equal("/AzureAD/Account/SignOut/AzureAD", cookieAuthenticationOptions.LogoutPath);
  75. Assert.Equal("/AzureAD/Account/AccessDenied", cookieAuthenticationOptions.AccessDeniedPath);
  76. Assert.Equal(SameSiteMode.None, cookieAuthenticationOptions.Cookie.SameSite);
  77. }
  78. [Fact]
  79. public void AddAzureAD_AllowsOverridingCookiesAndOpenIdConnectSettings()
  80. {
  81. // Arrange
  82. var services = new ServiceCollection();
  83. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  84. // Act
  85. services.AddAuthentication()
  86. .AddAzureAD(o =>
  87. {
  88. o.Instance = "https://login.microsoftonline.com";
  89. o.ClientId = "ClientId";
  90. o.ClientSecret = "ClientSecret";
  91. o.CallbackPath = "/signin-oidc";
  92. o.Domain = "domain.onmicrosoft.com";
  93. o.TenantId = "Common";
  94. });
  95. services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, o =>
  96. {
  97. o.Authority = "https://overriden.com";
  98. });
  99. services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme, o =>
  100. {
  101. o.AccessDeniedPath = "/Overriden";
  102. });
  103. var provider = services.BuildServiceProvider();
  104. // Assert
  105. var openIdOptionsMonitor = provider.GetService<IOptionsMonitor<OpenIdConnectOptions>>();
  106. Assert.NotNull(openIdOptionsMonitor);
  107. var openIdOptions = openIdOptionsMonitor.Get(AzureADDefaults.OpenIdScheme);
  108. Assert.Equal("ClientId", openIdOptions.ClientId);
  109. Assert.Equal($"https://overriden.com", openIdOptions.Authority);
  110. var cookieAuthenticationOptionsMonitor = provider.GetService<IOptionsMonitor<CookieAuthenticationOptions>>();
  111. Assert.NotNull(cookieAuthenticationOptionsMonitor);
  112. var cookieAuthenticationOptions = cookieAuthenticationOptionsMonitor.Get(AzureADDefaults.CookieScheme);
  113. Assert.Equal("/AzureAD/Account/SignIn/AzureAD", cookieAuthenticationOptions.LoginPath);
  114. Assert.Equal("/Overriden", cookieAuthenticationOptions.AccessDeniedPath);
  115. }
  116. [Fact]
  117. public void AddAzureAD_RegisteringAddCookiesAndAddOpenIdConnectHasNoImpactOnAzureAAExtensions()
  118. {
  119. // Arrange
  120. var services = new ServiceCollection();
  121. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  122. // Act
  123. services.AddAuthentication()
  124. .AddOpenIdConnect()
  125. .AddCookie()
  126. .AddAzureAD(o =>
  127. {
  128. o.Instance = "https://login.microsoftonline.com";
  129. o.ClientId = "ClientId";
  130. o.ClientSecret = "ClientSecret";
  131. o.CallbackPath = "/signin-oidc";
  132. o.Domain = "domain.onmicrosoft.com";
  133. o.TenantId = "Common";
  134. });
  135. services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, o =>
  136. {
  137. o.Authority = "https://overriden.com";
  138. });
  139. services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme, o =>
  140. {
  141. o.AccessDeniedPath = "/Overriden";
  142. });
  143. var provider = services.BuildServiceProvider();
  144. // Assert
  145. var openIdOptionsMonitor = provider.GetService<IOptionsMonitor<OpenIdConnectOptions>>();
  146. Assert.NotNull(openIdOptionsMonitor);
  147. var openIdOptions = openIdOptionsMonitor.Get(AzureADDefaults.OpenIdScheme);
  148. Assert.Equal("ClientId", openIdOptions.ClientId);
  149. Assert.Equal($"https://overriden.com", openIdOptions.Authority);
  150. var cookieAuthenticationOptionsMonitor = provider.GetService<IOptionsMonitor<CookieAuthenticationOptions>>();
  151. Assert.NotNull(cookieAuthenticationOptionsMonitor);
  152. var cookieAuthenticationOptions = cookieAuthenticationOptionsMonitor.Get(AzureADDefaults.CookieScheme);
  153. Assert.Equal("/AzureAD/Account/SignIn/AzureAD", cookieAuthenticationOptions.LoginPath);
  154. Assert.Equal("/Overriden", cookieAuthenticationOptions.AccessDeniedPath);
  155. }
  156. [Fact]
  157. public void AddAzureAD_ThrowsForDuplicatedSchemes()
  158. {
  159. // Arrange
  160. var services = new ServiceCollection();
  161. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  162. services.AddAuthentication()
  163. .AddAzureAD(o => { })
  164. .AddAzureAD(o => { });
  165. var provider = services.BuildServiceProvider();
  166. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  167. // Act & Assert
  168. var exception = Assert.Throws<InvalidOperationException>(
  169. () => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
  170. Assert.Equal("A scheme with the name 'AzureAD' was already added.", exception.Message);
  171. }
  172. [Fact]
  173. public void AddAzureAD_ThrowsWhenOpenIdSchemeIsAlreadyInUse()
  174. {
  175. // Arrange
  176. var services = new ServiceCollection();
  177. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  178. services.AddAuthentication()
  179. .AddAzureAD(o => { })
  180. .AddAzureAD("Custom", AzureADDefaults.OpenIdScheme, "Cookie", null, o => { });
  181. var provider = services.BuildServiceProvider();
  182. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  183. var expectedMessage = $"The Open ID Connect scheme 'AzureADOpenID' can't be associated with the Azure Active Directory scheme 'Custom'. " +
  184. "The Open ID Connect scheme 'AzureADOpenID' is already mapped to the Azure Active Directory scheme 'AzureAD'";
  185. // Act & Assert
  186. var exception = Assert.Throws<InvalidOperationException>(
  187. () => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
  188. Assert.Equal(expectedMessage, exception.Message);
  189. }
  190. [Fact]
  191. public void AddAzureAD_ThrowsWhenCookieSchemeIsAlreadyInUse()
  192. {
  193. // Arrange
  194. var services = new ServiceCollection();
  195. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  196. services.AddAuthentication()
  197. .AddAzureAD(o => { })
  198. .AddAzureAD("Custom", "OpenID", AzureADDefaults.CookieScheme, null, o => { });
  199. var provider = services.BuildServiceProvider();
  200. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  201. var expectedMessage = $"The cookie scheme 'AzureADCookie' can't be associated with the Azure Active Directory scheme 'Custom'. " +
  202. "The cookie scheme 'AzureADCookie' is already mapped to the Azure Active Directory scheme 'AzureAD'";
  203. // Act & Assert
  204. var exception = Assert.Throws<InvalidOperationException>(
  205. () => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
  206. Assert.Equal(expectedMessage, exception.Message);
  207. }
  208. [Fact]
  209. public void AddAzureAD_ThrowsWhenInstanceIsNotSet()
  210. {
  211. // Arrange
  212. var services = new ServiceCollection();
  213. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  214. services.AddAuthentication()
  215. .AddAzureAD(o => { });
  216. var provider = services.BuildServiceProvider();
  217. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  218. var expectedMessage = "The 'Instance' option must be provided.";
  219. // Act & Assert
  220. var exception = Assert.Throws<OptionsValidationException>(
  221. () => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
  222. Assert.Contains(expectedMessage, exception.Failures);
  223. }
  224. [Fact]
  225. public void AddAzureADBearer_AddsAllAuthenticationHandlers()
  226. {
  227. // Arrange
  228. var services = new ServiceCollection();
  229. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  230. // Act
  231. services.AddAuthentication()
  232. .AddAzureADBearer(o => { });
  233. var provider = services.BuildServiceProvider();
  234. // Assert
  235. Assert.NotNull(provider.GetService<JwtBearerHandler>());
  236. Assert.NotNull(provider.GetService<PolicySchemeHandler>());
  237. }
  238. [Fact]
  239. public void AddAzureADBearer_ConfiguresAllOptions()
  240. {
  241. // Arrange
  242. var services = new ServiceCollection();
  243. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  244. // Act
  245. services.AddAuthentication()
  246. .AddAzureADBearer(o =>
  247. {
  248. o.Instance = "https://login.microsoftonline.com/";
  249. o.ClientId = "ClientId";
  250. o.CallbackPath = "/signin-oidc";
  251. o.Domain = "domain.onmicrosoft.com";
  252. o.TenantId = "TenantId";
  253. });
  254. var provider = services.BuildServiceProvider();
  255. // Assert
  256. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  257. Assert.NotNull(azureADOptionsMonitor);
  258. var options = azureADOptionsMonitor.Get(AzureADDefaults.BearerAuthenticationScheme);
  259. Assert.Equal(AzureADDefaults.JwtBearerAuthenticationScheme, options.JwtBearerSchemeName);
  260. Assert.Equal("https://login.microsoftonline.com/", options.Instance);
  261. Assert.Equal("ClientId", options.ClientId);
  262. Assert.Equal("domain.onmicrosoft.com", options.Domain);
  263. var bearerOptionsMonitor = provider.GetService<IOptionsMonitor<JwtBearerOptions>>();
  264. Assert.NotNull(bearerOptionsMonitor);
  265. var bearerOptions = bearerOptionsMonitor.Get(AzureADDefaults.JwtBearerAuthenticationScheme);
  266. Assert.Equal("ClientId", bearerOptions.Audience);
  267. Assert.Equal($"https://login.microsoftonline.com/TenantId", bearerOptions.Authority);
  268. }
  269. [Fact]
  270. public void AddAzureADBearer_CanOverrideJwtBearerOptionsConfiguration()
  271. {
  272. // Arrange
  273. var services = new ServiceCollection();
  274. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  275. // Act
  276. services.AddAuthentication()
  277. .AddAzureADBearer(o =>
  278. {
  279. o.Instance = "https://login.microsoftonline.com/";
  280. o.ClientId = "ClientId";
  281. o.CallbackPath = "/signin-oidc";
  282. o.Domain = "domain.onmicrosoft.com";
  283. o.TenantId = "TenantId";
  284. });
  285. services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, o =>
  286. {
  287. o.Audience = "http://overriden.com";
  288. });
  289. var provider = services.BuildServiceProvider();
  290. // Assert
  291. var bearerOptionsMonitor = provider.GetService<IOptionsMonitor<JwtBearerOptions>>();
  292. Assert.NotNull(bearerOptionsMonitor);
  293. var bearerOptions = bearerOptionsMonitor.Get(AzureADDefaults.JwtBearerAuthenticationScheme);
  294. Assert.Equal("http://overriden.com", bearerOptions.Audience);
  295. Assert.Equal($"https://login.microsoftonline.com/TenantId", bearerOptions.Authority);
  296. }
  297. [Fact]
  298. public void AddAzureADBearer_RegisteringJwtBearerHasNoImpactOnAzureAAExtensions()
  299. {
  300. // Arrange
  301. var services = new ServiceCollection();
  302. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  303. // Act
  304. services.AddAuthentication()
  305. .AddJwtBearer()
  306. .AddAzureADBearer(o =>
  307. {
  308. o.Instance = "https://login.microsoftonline.com/";
  309. o.ClientId = "ClientId";
  310. o.CallbackPath = "/signin-oidc";
  311. o.Domain = "domain.onmicrosoft.com";
  312. o.TenantId = "TenantId";
  313. });
  314. services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, o =>
  315. {
  316. o.Audience = "http://overriden.com";
  317. });
  318. var provider = services.BuildServiceProvider();
  319. // Assert
  320. var bearerOptionsMonitor = provider.GetService<IOptionsMonitor<JwtBearerOptions>>();
  321. Assert.NotNull(bearerOptionsMonitor);
  322. var bearerOptions = bearerOptionsMonitor.Get(AzureADDefaults.JwtBearerAuthenticationScheme);
  323. Assert.Equal("http://overriden.com", bearerOptions.Audience);
  324. Assert.Equal($"https://login.microsoftonline.com/TenantId", bearerOptions.Authority);
  325. }
  326. [Fact]
  327. public void AddAzureADBearer_ThrowsForDuplicatedSchemes()
  328. {
  329. // Arrange
  330. var services = new ServiceCollection();
  331. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  332. services.AddAuthentication()
  333. .AddAzureADBearer(o => { })
  334. .AddAzureADBearer(o => { });
  335. var provider = services.BuildServiceProvider();
  336. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  337. // Act & Assert
  338. var exception = Assert.Throws<InvalidOperationException>(
  339. () => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
  340. Assert.Equal("A scheme with the name 'AzureADBearer' was already added.", exception.Message);
  341. }
  342. [Fact]
  343. public void AddAzureADBearer_ThrowsWhenBearerSchemeIsAlreadyInUse()
  344. {
  345. // Arrange
  346. var services = new ServiceCollection();
  347. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  348. services.AddAuthentication()
  349. .AddAzureADBearer(o => { })
  350. .AddAzureADBearer("Custom", AzureADDefaults.JwtBearerAuthenticationScheme, o => { });
  351. var provider = services.BuildServiceProvider();
  352. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  353. var expectedMessage = $"The JSON Web Token Bearer scheme 'AzureADJwtBearer' can't be associated with the Azure Active Directory scheme 'Custom'. " +
  354. "The JSON Web Token Bearer scheme 'AzureADJwtBearer' is already mapped to the Azure Active Directory scheme 'AzureADBearer'";
  355. // Act & Assert
  356. var exception = Assert.Throws<InvalidOperationException>(
  357. () => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
  358. Assert.Equal(expectedMessage, exception.Message);
  359. }
  360. [Fact]
  361. public void AddAzureADBearer_ThrowsWhenInstanceIsNotSet()
  362. {
  363. // Arrange
  364. var services = new ServiceCollection();
  365. services.AddSingleton<ILoggerFactory>(new NullLoggerFactory());
  366. services.AddAuthentication()
  367. .AddAzureADBearer(o => { });
  368. var provider = services.BuildServiceProvider();
  369. var azureADOptionsMonitor = provider.GetService<IOptionsMonitor<AzureADOptions>>();
  370. var expectedMessage = "The 'Instance' option must be provided.";
  371. // Act & Assert
  372. var exception = Assert.Throws<OptionsValidationException>(
  373. () => azureADOptionsMonitor.Get(AzureADDefaults.AuthenticationScheme));
  374. Assert.Contains(expectedMessage, exception.Failures);
  375. }
  376. }
  377. }