Skip to content

Commit e8aa5ff

Browse files
authored
Support [AllowAnonymous] along with [Authorize] (#1189)
1 parent 3556fe3 commit e8aa5ff

File tree

3 files changed

+34
-6
lines changed

3 files changed

+34
-6
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,8 @@ Both roles and policies are supported for output graph types, fields on output g
404404
and query arguments. If multiple policies are specified, all must match; if multiple roles
405405
are specified, any one role must match. You may also use `.Authorize()` and/or the
406406
`[Authorize]` attribute to validate that the user has authenticated. You may also use
407-
`.AllowAnonymous()` and/or `[AllowAnonymous]` to allow fields to be returned to
408-
unauthenticated users within an graph that has an authorization requirement defined.
407+
`.AllowAnonymous()` and/or `[AllowAnonymous]` to allow fields to bypass authorization
408+
requirements defined on the type that contains the field.
409409

410410
Please note that authorization rules do not apply to values returned within introspection requests,
411411
potentially leaking information about protected areas of the schema to unauthenticated users.

src/Transports.AspNetCore/AuthorizationVisitorBase.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,7 @@ public virtual async ValueTask EnterAsync(ASTNode node, ValidationContext contex
6565
_onlyAnonymousSelected.Push(ti);
6666

6767
// Fields, unlike types, are validated immediately.
68-
if (!fieldAnonymousAllowed)
69-
{
70-
await ValidateAsync(field, node, context);
71-
}
68+
await ValidateAsync(field, node, context);
7269
}
7370

7471
// prep for descendants, if any

tests/Transports.AspNetCore.Tests/AuthorizationTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,37 @@ public async Task EndToEnd(bool authenticated)
775775
actual.ShouldBe("""{"errors":[{"message":"Access denied for field \u0027parent\u0027 on type \u0027QueryType\u0027.","locations":[{"line":1,"column":3}],"extensions":{"code":"ACCESS_DENIED","codes":["ACCESS_DENIED"]}}]}""");
776776
}
777777

778+
[Theory]
779+
[InlineData("Role1", false, false)] // User with Role1, child requires Role2 - should fail at child level
780+
[InlineData("Role2", false, false)] // User with Role2, query requires Role1 - should fail at query level
781+
[InlineData("Role1,Role2", false, true)] // User with both roles - should pass
782+
[InlineData(null, false, false)] // Unauthenticated user - should fail at query level
783+
[InlineData("Role1", true, false)] // User with Role1, child requires Role2 and is anonymous - should fail
784+
[InlineData("Role2", true, true)] // User with Role2, child requires Role2 and is anonymous - should pass
785+
[InlineData("Role1,Role2", true, true)] // User with both roles, child is anonymous - should pass
786+
[InlineData(null, true, false)] // Unauthenticated user, child is anonymous - should fail as Role2 is missing
787+
public void BothAnonymousAndRequirements(string? userRoles, bool childIsAnonymous, bool expectedIsValid)
788+
{
789+
// Set up query to require Role1
790+
_query.AuthorizeWithRoles("Role1");
791+
792+
// Set up child field to require Role2 and optionally be anonymous
793+
_field.AuthorizeWithRoles("Role2");
794+
if (childIsAnonymous)
795+
_field.AllowAnonymous();
796+
797+
// Set up user principal based on test parameters
798+
if (userRoles != null)
799+
{
800+
var roles = userRoles.Split(',');
801+
var claims = roles.Select(role => new Claim(ClaimTypes.Role, role)).ToArray();
802+
_principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookie"));
803+
}
804+
805+
var ret = Validate(@"{ parent { child } }");
806+
ret.IsValid.ShouldBe(expectedIsValid);
807+
}
808+
778809
public enum Mode
779810
{
780811
None,

0 commit comments

Comments
 (0)