Skip to content

Commit dd56890

Browse files
authored
Support [AllowAnonymous] and [Authorize] simultaneously on a field (#88)
* Support [AllowAnonymous] and [Authorize] simultaneously on a field * update * fix formatting * update
1 parent e04baf0 commit dd56890

3 files changed

Lines changed: 33 additions & 5 deletions

File tree

README.md

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

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

src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ public virtual async ValueTask EnterAsync(ASTNode node, ValidationContext contex
5757
_onlyAnonymousSelected.Push(ti);
5858

5959
// Fields, unlike types, are validated immediately.
60-
if (!fieldAnonymousAllowed) {
61-
await ValidateAsync(field, node, context);
62-
}
60+
await ValidateAsync(field, node, context);
6361
}
6462

6563
// prep for descendants, if any

src/Tests/AuthorizationTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,36 @@ public async Task EndToEnd(bool authenticated)
751751
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""]}}]}");
752752
}
753753

754+
[Theory]
755+
[InlineData("Role1", false, false)] // User with Role1, child requires Role2 - should fail at child level
756+
[InlineData("Role2", false, false)] // User with Role2, query requires Role1 - should fail at query level
757+
[InlineData("Role1,Role2", false, true)] // User with both roles - should pass
758+
[InlineData(null, false, false)] // Unauthenticated user - should fail at query level
759+
[InlineData("Role1", true, false)] // User with Role1, child requires Role2 and is anonymous - should fail
760+
[InlineData("Role2", true, true)] // User with Role2, child requires Role2 and is anonymous - should pass
761+
[InlineData("Role1,Role2", true, true)] // User with both roles, child is anonymous - should pass
762+
[InlineData(null, true, false)] // Unauthenticated user, child is anonymous - should fail as Role2 is missing
763+
public void BothAnonymousAndRequirements(string? userRoles, bool childIsAnonymous, bool expectedIsValid)
764+
{
765+
// Set up query to require Role1
766+
_query.AuthorizeWithRoles("Role1");
767+
768+
// Set up child field to require Role2 and optionally be anonymous
769+
_field.AuthorizeWithRoles("Role2");
770+
if (childIsAnonymous)
771+
_field.AllowAnonymous();
772+
773+
// Set up user principal based on test parameters
774+
if (userRoles != null) {
775+
var roles = userRoles.Split(',');
776+
var claims = roles.Select(role => new Claim(ClaimTypes.Role, role)).ToArray();
777+
_principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookie"));
778+
}
779+
780+
var ret = Validate(@"{ parent { child } }");
781+
ret.IsValid.ShouldBe(expectedIsValid);
782+
}
783+
754784
public enum Mode
755785
{
756786
None,

0 commit comments

Comments
 (0)