33using System . Security . Claims ;
44using GraphQL . Execution ;
55using GraphQL . Server . Transports . AspNetCore . Errors ;
6+ using Microsoft . AspNetCore . Authentication . Cookies ;
67using Microsoft . AspNetCore . Authentication . JwtBearer ;
78
89namespace Tests . Middleware ;
@@ -11,9 +12,14 @@ public class AuthorizationTests : IDisposable
1112{
1213 private GraphQLHttpMiddlewareOptions _options = null ! ;
1314 private bool _enableCustomErrorInfoProvider ;
14- private readonly TestServer _server ;
15+ private TestServer _server ;
1516
1617 public AuthorizationTests ( )
18+ {
19+ _server = CreateServer ( ) ;
20+ }
21+
22+ private TestServer CreateServer ( Action < IServiceCollection > ? configureServices = null )
1723 {
1824 var hostBuilder = new WebHostBuilder ( ) ;
1925 hostBuilder . ConfigureServices ( services =>
@@ -46,6 +52,7 @@ public AuthorizationTests()
4652#if NETCOREAPP2_1 || NET48
4753 services . AddHostApplicationLifetime ( ) ;
4854#endif
55+ configureServices ? . Invoke ( services ) ;
4956 } ) ;
5057 hostBuilder . Configure ( app =>
5158 {
@@ -59,7 +66,7 @@ public AuthorizationTests()
5966 _options = opts ;
6067 } ) ;
6168 } ) ;
62- _server = new TestServer ( hostBuilder ) ;
69+ return new TestServer ( hostBuilder ) ;
6370 }
6471
6572 public void Dispose ( ) => _server . Dispose ( ) ;
@@ -270,6 +277,95 @@ public async Task Authorized_Policy()
270277 actual . ShouldBe ( """{"data":{"__typename":"Query"}}""" ) ;
271278 }
272279
280+ [ Fact ]
281+ public async Task NotAuthorized_WrongScheme ( )
282+ {
283+ _server . Dispose ( ) ;
284+ _server = CreateServer ( services =>
285+ {
286+ services . AddAuthentication ( CookieAuthenticationDefaults . AuthenticationScheme ) ; // change default scheme to Cookie authentication
287+ } ) ;
288+ _options . AuthorizationRequired = true ;
289+ using var response = await PostQueryAsync ( "{ __typename }" , true ) ; // send an authenticated request (with JWT bearer scheme)
290+ response . StatusCode . ShouldBe ( HttpStatusCode . Unauthorized ) ;
291+ var actual = await response . Content . ReadAsStringAsync ( ) ;
292+ actual . ShouldBe ( @"{""errors"":[{""message"":""Access denied for schema."",""extensions"":{""code"":""ACCESS_DENIED"",""codes"":[""ACCESS_DENIED""]}}]}" ) ;
293+ }
294+
295+ [ Fact ]
296+ public async Task NotAuthorized_WrongScheme_2 ( )
297+ {
298+ _server . Dispose ( ) ;
299+ _server = CreateServer ( services =>
300+ {
301+ services . AddAuthentication ( ) . AddCookie ( ) ; // add Cookie authentication
302+ } ) ;
303+ _options . AuthorizationRequired = true ;
304+ _options . AuthenticationSchemes . Add ( CookieAuthenticationDefaults . AuthenticationScheme ) ; // change authentication scheme for GraphQL requests to Cookie (which is not used by the test client)
305+ using var response = await PostQueryAsync ( "{ __typename }" , true ) ; // send an authenticated request (with JWT bearer scheme)
306+ response . StatusCode . ShouldBe ( HttpStatusCode . Unauthorized ) ;
307+ var actual = await response . Content . ReadAsStringAsync ( ) ;
308+ actual . ShouldBe ( @"{""errors"":[{""message"":""Access denied for schema."",""extensions"":{""code"":""ACCESS_DENIED"",""codes"":[""ACCESS_DENIED""]}}]}" ) ;
309+ }
310+
311+ [ Fact ]
312+ public async Task NotAuthorized_WrongScheme_VerifyUser ( )
313+ {
314+ bool validatedUser = false ;
315+ _server . Dispose ( ) ;
316+ _server = CreateServer ( services =>
317+ {
318+ services . AddAuthentication ( CookieAuthenticationDefaults . AuthenticationScheme ) ; // change default scheme to Cookie authentication
319+ services . AddGraphQL ( b => b
320+ . ConfigureExecutionOptions ( opts =>
321+ {
322+ opts . User . ShouldNotBeNull ( ) . Identity . ShouldNotBeNull ( ) . IsAuthenticated . ShouldBeFalse ( ) ;
323+ validatedUser = true ;
324+ } ) ) ;
325+ } ) ;
326+ _options . AuthorizationRequired = false ; // disable authorization requirements; we just want to verify that an anonymous user is passed to the execution options
327+ using var response = await PostQueryAsync ( "{ __typename }" , true ) ; // send an authenticated request (with JWT bearer scheme)
328+ response . StatusCode . ShouldBe ( HttpStatusCode . OK ) ;
329+ var actual = await response . Content . ReadAsStringAsync ( ) ;
330+ actual . ShouldBe ( @"{""data"":{""__typename"":""Query""}}" ) ;
331+ validatedUser . ShouldBeTrue ( ) ;
332+ }
333+
334+ [ Fact ]
335+ public async Task Authorized_DifferentScheme ( )
336+ {
337+ bool validatedUser = false ;
338+ _server . Dispose ( ) ;
339+ _server = CreateServer ( services =>
340+ {
341+ services . AddAuthentication ( CookieAuthenticationDefaults . AuthenticationScheme ) ; // change default scheme to Cookie authentication
342+ services . AddGraphQL ( b => b . ConfigureExecutionOptions ( opts =>
343+ {
344+ opts . User . ShouldNotBeNull ( ) . Identity . ShouldNotBeNull ( ) . IsAuthenticated . ShouldBeTrue ( ) ;
345+ validatedUser = true ;
346+ } ) ) ;
347+ } ) ;
348+ _options . AuthorizationRequired = true ;
349+ _options . AuthenticationSchemes . Add ( JwtBearerDefaults . AuthenticationScheme ) ;
350+ using var response = await PostQueryAsync ( "{ __typename }" , true ) ;
351+ response . StatusCode . ShouldBe ( HttpStatusCode . OK ) ;
352+ var actual = await response . Content . ReadAsStringAsync ( ) ;
353+ actual . ShouldBe ( @"{""data"":{""__typename"":""Query""}}" ) ;
354+ validatedUser . ShouldBeTrue ( ) ;
355+ }
356+
357+ [ Fact ]
358+ public void SecurityHelperTests ( )
359+ {
360+ SecurityHelper . MergeUserPrincipal ( null , null ) . ShouldNotBeNull ( ) . Identity . ShouldBeNull ( ) ; // Note that ASP.NET Core does not return null for anonymous user
361+ var principal1 = new ClaimsPrincipal ( new ClaimsIdentity ( ) ) ; // empty identity for primary identity (default for ASP.NET Core)
362+ SecurityHelper . MergeUserPrincipal ( null , principal1 ) . ShouldBe ( principal1 ) ;
363+ var principal2 = new ClaimsPrincipal ( new ClaimsIdentity ( "test1" ) ) ; // non-empty identity for secondary identity
364+ SecurityHelper . MergeUserPrincipal ( principal1 , principal2 ) . Identities . ShouldHaveSingleItem ( ) . AuthenticationType . ShouldBe ( "test1" ) ;
365+ var principal3 = new ClaimsPrincipal ( new ClaimsIdentity ( "test2" ) ) ; // merge two non-empty identities together
366+ SecurityHelper . MergeUserPrincipal ( principal2 , principal3 ) . Identities . Select ( x => x . AuthenticationType ) . ShouldBe ( new [ ] { "test2" , "test1" } ) ; // last one wins
367+ }
368+
273369 private class CustomErrorInfoProvider : ErrorInfoProvider
274370 {
275371 private readonly AuthorizationTests _authorizationTests ;
0 commit comments