Skip to content

Commit b9fcd82

Browse files
authored
Improve exception messages for missing auth services (#38045)
1 parent aa474ee commit b9fcd82

File tree

8 files changed

+152
-30
lines changed

8 files changed

+152
-30
lines changed

src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Security.Claims;
5-
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Authentication.Abstractions;
66
using Microsoft.AspNetCore.Http;
77
using Microsoft.Extensions.DependencyInjection;
88

@@ -29,7 +29,7 @@ public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext contex
2929
/// <param name="scheme">The name of the authentication scheme.</param>
3030
/// <returns>The <see cref="AuthenticateResult"/>.</returns>
3131
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string? scheme) =>
32-
context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);
32+
GetAuthenticationService(context).AuthenticateAsync(context, scheme);
3333

3434
/// <summary>
3535
/// Challenge the current request using the specified scheme.
@@ -71,7 +71,7 @@ public static Task ChallengeAsync(this HttpContext context, AuthenticationProper
7171
/// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
7272
/// <returns>The task.</returns>
7373
public static Task ChallengeAsync(this HttpContext context, string? scheme, AuthenticationProperties? properties) =>
74-
context.RequestServices.GetRequiredService<IAuthenticationService>().ChallengeAsync(context, scheme, properties);
74+
GetAuthenticationService(context).ChallengeAsync(context, scheme, properties);
7575

7676
/// <summary>
7777
/// Forbid the current request using the specified scheme.
@@ -113,7 +113,7 @@ public static Task ForbidAsync(this HttpContext context, AuthenticationPropertie
113113
/// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
114114
/// <returns>The task.</returns>
115115
public static Task ForbidAsync(this HttpContext context, string? scheme, AuthenticationProperties? properties) =>
116-
context.RequestServices.GetRequiredService<IAuthenticationService>().ForbidAsync(context, scheme, properties);
116+
GetAuthenticationService(context).ForbidAsync(context, scheme, properties);
117117

118118
/// <summary>
119119
/// Sign in a principal for the specified scheme.
@@ -155,7 +155,7 @@ public static Task SignInAsync(this HttpContext context, ClaimsPrincipal princip
155155
/// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
156156
/// <returns>The task.</returns>
157157
public static Task SignInAsync(this HttpContext context, string? scheme, ClaimsPrincipal principal, AuthenticationProperties? properties) =>
158-
context.RequestServices.GetRequiredService<IAuthenticationService>().SignInAsync(context, scheme, principal, properties);
158+
GetAuthenticationService(context).SignInAsync(context, scheme, principal, properties);
159159

160160
/// <summary>
161161
/// Sign out a principal for the default authentication scheme.
@@ -190,7 +190,7 @@ public static Task SignInAsync(this HttpContext context, string? scheme, ClaimsP
190190
/// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
191191
/// <returns>The task.</returns>
192192
public static Task SignOutAsync(this HttpContext context, string? scheme, AuthenticationProperties? properties) =>
193-
context.RequestServices.GetRequiredService<IAuthenticationService>().SignOutAsync(context, scheme, properties);
193+
GetAuthenticationService(context).SignOutAsync(context, scheme, properties);
194194

195195
/// <summary>
196196
/// Authenticates the request using the specified scheme and returns the value for the token.
@@ -200,7 +200,7 @@ public static Task SignOutAsync(this HttpContext context, string? scheme, Authen
200200
/// <param name="tokenName">The name of the token.</param>
201201
/// <returns>The value of the token if present.</returns>
202202
public static Task<string?> GetTokenAsync(this HttpContext context, string? scheme, string tokenName) =>
203-
context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, scheme, tokenName);
203+
GetAuthenticationService(context).GetTokenAsync(context, scheme, tokenName);
204204

205205
/// <summary>
206206
/// Authenticates the request using the default authentication scheme and returns the value for the token.
@@ -210,6 +210,14 @@ public static Task SignOutAsync(this HttpContext context, string? scheme, Authen
210210
/// <param name="tokenName">The name of the token.</param>
211211
/// <returns>The value of the token if present.</returns>
212212
public static Task<string?> GetTokenAsync(this HttpContext context, string tokenName) =>
213-
context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, tokenName);
213+
GetAuthenticationService(context).GetTokenAsync(context, tokenName);
214+
215+
// This project doesn't reference AuthenticationServiceCollectionExtensions.AddAuthentication so we use a string.
216+
private static IAuthenticationService GetAuthenticationService(HttpContext context) =>
217+
context.RequestServices.GetService<IAuthenticationService>() ??
218+
throw new InvalidOperationException(Resources.FormatException_UnableToFindServices(
219+
nameof(IAuthenticationService),
220+
nameof(IServiceCollection),
221+
"AddAuthentication"));
214222
}
215223
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<!--
4+
Microsoft ResX Schema
5+
6+
Version 2.0
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
11+
associated with the data types.
12+
13+
Example:
14+
15+
... ado.net/XML headers & schema ...
16+
<resheader name="resmimetype">text/microsoft-resx</resheader>
17+
<resheader name="version">2.0</resheader>
18+
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19+
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20+
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21+
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22+
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23+
<value>[base64 mime encoded serialized .NET Framework object]</value>
24+
</data>
25+
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26+
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27+
<comment>This is a comment</comment>
28+
</data>
29+
30+
There are any number of "resheader" rows that contain simple
31+
name/value pairs.
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
37+
mimetype set.
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
41+
extensible. For a given mimetype the value must be set accordingly:
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
45+
read any of the formats listed below.
46+
47+
mimetype: application/x-microsoft.net.object.binary.base64
48+
value : The object must be serialized with
49+
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50+
: and then encoded with base64 encoding.
51+
52+
mimetype: application/x-microsoft.net.object.soap.base64
53+
value : The object must be serialized with
54+
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55+
: and then encoded with base64 encoding.
56+
57+
mimetype: application/x-microsoft.net.object.bytearray.base64
58+
value : The object must be serialized into a byte array
59+
: using a System.ComponentModel.TypeConverter
60+
: and then encoded with base64 encoding.
61+
-->
62+
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63+
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64+
<xsd:element name="root" msdata:IsDataSet="true">
65+
<xsd:complexType>
66+
<xsd:choice maxOccurs="unbounded">
67+
<xsd:element name="metadata">
68+
<xsd:complexType>
69+
<xsd:sequence>
70+
<xsd:element name="value" type="xsd:string" minOccurs="0" />
71+
</xsd:sequence>
72+
<xsd:attribute name="name" use="required" type="xsd:string" />
73+
<xsd:attribute name="type" type="xsd:string" />
74+
<xsd:attribute name="mimetype" type="xsd:string" />
75+
<xsd:attribute ref="xml:space" />
76+
</xsd:complexType>
77+
</xsd:element>
78+
<xsd:element name="assembly">
79+
<xsd:complexType>
80+
<xsd:attribute name="alias" type="xsd:string" />
81+
<xsd:attribute name="name" type="xsd:string" />
82+
</xsd:complexType>
83+
</xsd:element>
84+
<xsd:element name="data">
85+
<xsd:complexType>
86+
<xsd:sequence>
87+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88+
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89+
</xsd:sequence>
90+
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91+
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92+
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93+
<xsd:attribute ref="xml:space" />
94+
</xsd:complexType>
95+
</xsd:element>
96+
<xsd:element name="resheader">
97+
<xsd:complexType>
98+
<xsd:sequence>
99+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100+
</xsd:sequence>
101+
<xsd:attribute name="name" type="xsd:string" use="required" />
102+
</xsd:complexType>
103+
</xsd:element>
104+
</xsd:choice>
105+
</xsd:complexType>
106+
</xsd:element>
107+
</xsd:schema>
108+
<resheader name="resmimetype">
109+
<value>text/microsoft-resx</value>
110+
</resheader>
111+
<resheader name="version">
112+
<value>2.0</value>
113+
</resheader>
114+
<resheader name="reader">
115+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116+
</resheader>
117+
<resheader name="writer">
118+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119+
</resheader>
120+
<data name="Exception_UnableToFindServices" xml:space="preserve">
121+
<value>Unable to find the required '{0}' service. Please add all the required services by calling '{1}.{2}' in the application startup code.</value>
122+
</data>
123+
</root>

src/Http/Routing/src/EndpointMiddleware.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Threading.Tasks;
64
using Microsoft.AspNetCore.Authorization;
75
using Microsoft.AspNetCore.Cors.Infrastructure;
86
using Microsoft.AspNetCore.Http;
@@ -90,15 +88,15 @@ private static void ThrowMissingAuthMiddlewareException(Endpoint endpoint)
9088
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains authorization metadata, " +
9189
"but a middleware was not found that supports authorization." +
9290
Environment.NewLine +
93-
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).");
91+
"Configure your application startup by adding app.UseAuthorization() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.");
9492
}
9593

9694
private static void ThrowMissingCorsMiddlewareException(Endpoint endpoint)
9795
{
9896
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains CORS metadata, " +
9997
"but a middleware was not found that supports CORS." +
10098
Environment.NewLine +
101-
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. The call to app.UseCors() must appear between app.UseRouting() and app.UseEndpoints(...).");
99+
"Configure your application startup by adding app.UseCors() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.");
102100
}
103101

104102
private static partial class Log

src/Http/Routing/test/FunctionalTests/EndpointRoutingIntegrationTest.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ public class EndpointRoutingIntegrationTest
2020
private static readonly RequestDelegate TestDelegate = async context => await Task.Yield();
2121
private static readonly string AuthErrorMessage = "Endpoint / contains authorization metadata, but a middleware was not found that supports authorization." +
2222
Environment.NewLine +
23-
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. " +
24-
"The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).";
23+
"Configure your application startup by adding app.UseAuthorization() in the application startup code. " +
24+
"If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.";
2525

2626
private static readonly string CORSErrorMessage = "Endpoint / contains CORS metadata, but a middleware was not found that supports CORS." +
2727
Environment.NewLine +
28-
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. " +
29-
"The call to app.UseCors() must appear between app.UseRouting() and app.UseEndpoints(...).";
28+
"Configure your application startup by adding app.UseCors() in the application startup code. " +
29+
"If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.";
3030

3131
[Fact]
3232
public async Task AuthorizationMiddleware_WhenNoAuthMetadataIsConfigured()

src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Threading.Tasks;
64
using Microsoft.AspNetCore.Authorization;
75
using Microsoft.AspNetCore.Cors.Infrastructure;
86
using Microsoft.AspNetCore.Http;
9-
using Microsoft.AspNetCore.Http.Features;
107
using Microsoft.Extensions.Logging.Abstractions;
118
using Microsoft.Extensions.Options;
129
using Moq;
13-
using Xunit;
1410

1511
namespace Microsoft.AspNetCore.Routing
1612
{
@@ -101,8 +97,8 @@ public async Task Invoke_WithEndpoint_ThrowsIfAuthAttributesWereFound_ButAuthMid
10197
// Arrange
10298
var expected = "Endpoint Test contains authorization metadata, but a middleware was not found that supports authorization." +
10399
Environment.NewLine +
104-
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. " +
105-
"The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).";
100+
"Configure your application startup by adding app.UseAuthorization() in the application startup code. " +
101+
"If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.";
106102
var httpContext = new DefaultHttpContext
107103
{
108104
RequestServices = new ServiceProvider()
@@ -198,8 +194,8 @@ public async Task Invoke_WithEndpoint_ThrowsIfCorsMetadataWasFound_ButCorsMiddle
198194
// Arrange
199195
var expected = "Endpoint Test contains CORS metadata, but a middleware was not found that supports CORS." +
200196
Environment.NewLine +
201-
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. " +
202-
"The call to app.UseCors() must appear between app.UseRouting() and app.UseEndpoints(...).";
197+
"Configure your application startup by adding app.UseCors() in the application startup code. " +
198+
"If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.";
203199
var httpContext = new DefaultHttpContext
204200
{
205201
RequestServices = new ServiceProvider()

src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
54
using Microsoft.AspNetCore.Authorization;
65
using Microsoft.AspNetCore.Authorization.Policy;
76
using Microsoft.Extensions.DependencyInjection;
@@ -42,8 +41,7 @@ private static void VerifyServicesRegistered(IApplicationBuilder app)
4241
{
4342
throw new InvalidOperationException(Resources.FormatException_UnableToFindServices(
4443
nameof(IServiceCollection),
45-
nameof(PolicyServiceCollectionExtensions.AddAuthorization),
46-
"ConfigureServices(...)"));
44+
nameof(PolicyServiceCollectionExtensions.AddAuthorization)));
4745
}
4846
}
4947
}

src/Security/Authorization/Policy/src/Resources.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,6 @@
118118
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119
</resheader>
120120
<data name="Exception_UnableToFindServices" xml:space="preserve">
121-
<value>Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code.</value>
121+
<value>Unable to find the required services. Please add all the required services by calling '{0}.{1}' in the application startup code.</value>
122122
</data>
123123
</root>

src/Security/Authorization/test/AuthorizationAppBuilderExtensionsTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ public void UseAuthorization_MissingRequiredSevices_FriendlyErrorMessage()
6161
// Assert
6262
Assert.Equal(
6363
"Unable to find the required services. Please add all the required services by calling " +
64-
"'IServiceCollection.AddAuthorization' inside the call to 'ConfigureServices(...)' " +
65-
"in the application startup code.",
64+
"'IServiceCollection.AddAuthorization' in the application startup code.",
6665
ex.Message);
6766
}
6867

0 commit comments

Comments
 (0)