Comments (29)
Here is a sample of the latter:
public class MyAuthorizationVisitor : AuthorizationVisitor
{
public MyAuthorizationVisitor(ValidationContext context, ClaimsPrincipal claimsPrincipal, IAuthorizationService authorizationService)
: base(context, claimsPrincipal, authorizationService)
{
}
protected override bool IsAuthenticated => true;
}
public class MyAuthorizationRule : IValidationRule
{
public virtual async ValueTask<INodeVisitor?> ValidateAsync(ValidationContext context)
{
var user = context.User
?? throw new InvalidOperationException("User could not be retrieved from ValidationContext. Please be sure it is set in ExecutionOptions.User.");
var provider = context.RequestServices
?? throw new MissingRequestServicesException();
var authService = provider.GetService<IAuthorizationService>()
?? throw new InvalidOperationException("An instance of IAuthorizationService could not be pulled from the dependency injection framework.");
var visitor = new MyAuthorizationVisitor(context, user, authService);
// if the schema fails authentication, report the error and do not perform any additional authorization checks.
return await visitor.ValidateSchemaAsync(context) ? visitor : null;
}
}
builder.AddValidationRule<MyAuthorizationRule>();
// remove builder.AddAuthorizationRule()
from server.
Can you post a list of the GraphQL nuget packages you have installed ?
from server.
Thanks for you.
GraphQL.Server.All 7.2.0
from server.
IMS_Schemas:
public class IMS_Schemas : Schema
{
public IMS_Schemas(IServiceProvider serviceProvider) : base(serviceProvider)
{
Query = new AutoRegisteringObjectGraphType<Query>();
}
}
from server.
I tested the code you provided and it seems to work for me. See PR #994 -- all tests pass, both successful and access-denied tests for the modified sample project:
- Samples.Authorization.Tests.EndToEndTests.GraphQLGet_Success
- Samples.Authorization.Tests.EndToEndTests.GraphQLGet_AccessDenied
- Samples.Authorization.Tests.EndToEndTests.GraphQLPost_Success
- Samples.Authorization.Tests.EndToEndTests.GraphQLWebSocket_AccessDenied
- Samples.Authorization.Tests.EndToEndTests.GraphQLWebSocket_AccessDenied
Can you paste the app.UseGraphQL()
call(s) that you have?
Can you verify that your [Authorize]
attribute references GraphQL.AuthorizeAttribute
and not Microsoft.AspNetCore.Authorization.AuthorizeAttribute
?
Can you verify that you are not referencing any other GraphQL nuget packages?
from server.
app.UseGraphQL<IMS_Schemas>("/IMS_Schemas/graphql");
app.UseGraphQLPlayground(
"/IMS_Schemas/ui/playground",
new GraphQL.Server.Ui.Playground.PlaygroundOptions
{
GraphQLEndPoint = "../graphql",
SubscriptionsEndPoint = "../graphql",
});
[Authorize] attribute references GraphQL.GraphQLAttribute
Thank for you.
from server.
<PackageReference Include="GraphQL.Server.All" Version="7.2.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.2.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
from server.
Program.cs:
using GraphQL;
using KDRun.Model.GraphType.Common.Base;
using KDRun.Model.GraphType.IMS;
using KDRun.Service.ServiceAPI;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("111", policy =>
policy.Requirements.Add(new MinimumAgeRequirement()));
});
builder.Services.AddTransient<IAuthorizationHandler, MinimumAgeHandler>();
builder.Services.AddRazorPages();
builder.Services.AddGraphQL(b => b
.AddSchema<Common_Base_Schemas>()
.AddSchema<IMS_Schemas>()
.AddAutoClrMappings()
.AddSystemTextJson()
.AddAuthorizationRule());
//builder.Services.AddRouting();
//builder.Services.AddCors(options => options.AddPolicy("all", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
var app = builder.Build();
app.UseDeveloperExceptionPage();
app.UseAuthentication();
app.UseAuthorization();
app.UseWebSockets();
//app.UseRouting();
//app.UseCors("all");
// configure the graphql endpoint at "/Common_Base_Schemas/graphql"
app.UseGraphQL<Common_Base_Schemas>("/Common_Base_Schemas/graphql");
// configure Playground at "/Common_Base_Schemas/ui/playground" with relative link to api
app.UseGraphQLPlayground(
"/Common_Base_Schemas/ui/playground",
new GraphQL.Server.Ui.Playground.PlaygroundOptions
{
GraphQLEndPoint = "../graphql",
SubscriptionsEndPoint = "../graphql",
});
// configure the graphql endpoint at "/IMS_Schemas/graphql"
app.UseGraphQL<IMS_Schemas>("/IMS_Schemas/graphql");
// configure Playground at "/IMS_Schemas/ui/playground" with relative link to api
app.UseGraphQLPlayground(
"/IMS_Schemas/ui/playground",
new GraphQL.Server.Ui.Playground.PlaygroundOptions
{
GraphQLEndPoint = "../graphql",
SubscriptionsEndPoint = "../graphql",
});
app.MapRazorPages();
await app.RunAsync();
from server.
Query.cs:
using GraphQL;
namespace KDRun.Model.GraphType.IMS
{
public class Query
{
[AllowAnonymous]
public static CompanyEntity? companyEntity()
{
return new CompanyEntity() { rowkey = Guid.NewGuid().ToString(), CompanyId = "11", CompanyName = "12" };
}
[Authorize(Policy = "111")]
public static IEnumerable<CompanyEntity>? companyAll()
{
return new CompanyEntity[] { new CompanyEntity() { rowkey = Guid.NewGuid().ToString(), CompanyId = "A11" } };
}
}
from server.
I just realized what the issue is. Your authorization policy does not require the user to be authenticated, as it always returns Success
. However, in our authorization attribute, we implicitly require the user to be authenticated before policies or roles are checked. See here:
And here:
And here:
And here:
And here:
This was implemented as an optimization so that policies need not be checked if the user is not authenticated in the first place. This may differ from ASP.NET Core's logic.
We can consider changing this in a future version of GraphQL.NET if you can provide reasoning why this should not be the case.
To change behavior in the current version of GraphQL.NET, you would need to:
-
Inherit from
AuthorizationVisitor
and overrideValidateAsync
so it calls a custom implementation ofIsAuthorizationRequired
which only returnsprovider.GetMetadata(AUTHORIZE_KEY, false)
. See https://github.com/graphql-dotnet/server/blob/master/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs#L74 -
Inherit from
AuthorizationValidationRule
so it uses your customAuthorizationVisitor
implementation -
Call
.AddValidationRule<MyAuthorizationValidationRule>()
instead of.AddAuthorizationRule()
-
Write a custom
AuthorizationAttribute
which sets the metadata directly instead of callingAuthorizeWithPolicy
andAuthorizeWithRole
. -
Use the custom attribute on your methods.
from server.
Another option is to use the authorization project. There are some limitations, such as AllowAnonymous
and roles not being supported, but was designed to be able to be implemented without ASP.NET Core's authorization framework. I'm pretty sure it ignores the AUTHORIZE_KEY
metadata (currently), and so would just run any policies through your implementation. In the future we may update the code to have the same logic as the one in this project, at which time it would not work for you anymore.
from server.
Another option would be to derive from AuthorizationValidationVisitor
, and override IsAuthenticated
to always return true
. This is very simple to implement, so long as you have a policy or role defined for each method requiring authorization (and not just [Authorize]
). Of course methods not requiring authentication would continue to work as intended.
from server.
Thank you for your reply. In our plan, the authentication framework of .net core is not used. We customize the authentication process and combine it with radis cluster. When the user accesses a specific method, we use the custom authentication to check, that it is legal Let it go, and we will terminate the access if it is illegal.
from server.
Then perhaps you would just take the sample above, and instead of deriving from AuthorizationValidationRule
, derive from AuthorizationValidationRuleBase
and override all three methods: IsAuthenticated
, IsInRole
and AuthorizeAsync
to fit your needs, skipping the ASP.NET Core authorization framework altogether.
from server.
Sample of the latter:
public class MyAuthorizationVisitor : AuthorizationVisitorBase
{
public MyAuthorizationVisitor(ValidationContext context)
: base(context)
{
// optional: code to load the identity of the user here
}
protected override bool IsAuthenticated => false; // implement your logic here
protected override bool IsInRole(string role) => false; // implement your logic here
protected override ValueTask<AuthorizationResult> AuthorizeAsync(string policy)
=> new(AuthorizationResult.Failed()); // implement your logic here
}
public class MyAuthorizationRule : IValidationRule
{
// this is a singleton so there should be no user-specific code in the constructor
public virtual async ValueTask<INodeVisitor?> ValidateAsync(ValidationContext context)
{
var visitor = new MyAuthorizationVisitor(context);
// if the schema fails authentication, report the error and do not perform any additional authorization checks.
return await visitor.ValidateSchemaAsync(context) ? visitor : null;
}
}
builder.AddValidationRule<MyAuthorizationRule>();
from server.
Please note that if you add transport-level authentication requirements within the options of UseGraphQL
, then you need to override the middleware authentication method(s) to tie it to your own implementation. However, you can instead just set an authorization policy on a schema or on the Query or Mutation or Subscription type as appropriate and just use your modified validation rule.
from server.
Thanks for your reply, I'll try it!
from server.
Thank you for your reply,
How to access http headers in Query or Mutation or Subscription method?
and
How to access http headers in MyAuthorizationRule?
thanks.
from server.
Inject IHttpContextAccessor into the constructor of MyAuthorizationRule
from server.
Yes,
public class MyAuthorizationRule : IValidationRule
{
IHttpContextAccessor _httpContext;
public MyAuthorizationRule(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
// this is a singleton so there should be no user-specific code in the constructor
public virtual async ValueTask<INodeVisitor?> ValidateAsync(ValidationContext context)
{
var visitor = new MyAuthorizationVisitor(context);
Console.Write(_httpContext.HttpContext.Request.Headers);
// if the schema fails authentication, report the error and do not perform any additional authorization checks.
//return await visitor.ValidateSchemaAsync(context) ? visitor : null;
return null;
}
}
from server.
I’d probably just pass the relevant header(s) into the visitor, but there’s a few ways to implement it. Is it working?
from server.
Yes, nject IHttpContextAccessor into the constructor of MyAuthorizationRule is good.
from server.
Another option is to use the user context builder to initialize the UserContext based on the HttpContext. Then both the validation rule and individual methods have access to it.
from server.
That's a good idea, how to do this:
"Another option is to use the user context builder to initialize the UserContext based on the HttpContext. Then both the validation rule and individual methods have access to it.",
from server.
https://github.com/graphql-dotnet/server#user-context-configuration
from server.
Thank for you.
from server.
Here is a sample of the latter:
public class MyAuthorizationVisitor : AuthorizationVisitor { public MyAuthorizationVisitor(ValidationContext context, ClaimsPrincipal claimsPrincipal, IAuthorizationService authorizationService) : base(context, claimsPrincipal, authorizationService) { } protected override bool IsAuthenticated => true; } public class MyAuthorizationRule : IValidationRule { public virtual async ValueTask<INodeVisitor?> ValidateAsync(ValidationContext context) { var user = context.User ?? throw new InvalidOperationException("User could not be retrieved from ValidationContext. Please be sure it is set in ExecutionOptions.User."); var provider = context.RequestServices ?? throw new MissingRequestServicesException(); var authService = provider.GetService<IAuthorizationService>() ?? throw new InvalidOperationException("An instance of IAuthorizationService could not be pulled from the dependency injection framework."); var visitor = new MyAuthorizationVisitor(context, user, authService); // if the schema fails authentication, report the error and do not perform any additional authorization checks. return await visitor.ValidateSchemaAsync(context) ? visitor : null; } } builder.AddValidationRule<MyAuthorizationRule>(); // remove builder.AddAuthorizationRule()
Thank you. I just wanted to comment that I had the same problem building an internal enterprise application. This particular piece of code solved my problem.
from server.
@sungam3r Perhaps we should not check that IsAuthenticated == true
before checking policy or role membership.
from server.
I just ran into the same issue. Setting IsAuthenticated == true solved it, but finding the reason for this behaviour caused some headaches.
from server.
Related Issues (20)
- Using Altair UI while not connected to the internet HOT 3
- How to use plugins in Altair? HOT 6
- Duende Identity Server 4 / GraphQl Authorize - Always error 403 HOT 16
- Missing classes while upgrading to GraphQL.Server.All from GraphQL.Server.* libraries HOT 11
- Disable ReadFormOnPost by default
- Consider adding CSRF protection for GET requests
- Content Security Policy and CDN Scripts HOT 7
- I'm attempting to implement a dual authentication scheme and I'm wondering whether it's feasible. If it is possible, could you assist me by pointing out any mistakes I might be making? HOT 13
- Invalid HTML HOT 2
- [Feature] Add proper support of Accept HTTP header HOT 2
- Root Query Issue HOT 4
- Return 406 on unsupported Accept header
- Come up with something for comments about Accept header
- Question: Extensibility to use other transport protocol? HOT 5
- Question about upgrading from 6xx to 7xx: VoyagerOptions HOT 4
- Subscriptions set-up netcore 6.0, GraphQL 7.1.1 HOT 6
- HttpStatus Code on GRAPHQL_VALIDATION error HOT 6
- Is there a way to change the status code of the authorization extension from 400 to 401? HOT 10
- Validation rules and Policies errors after update to v7 HOT 11
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from server.