Comments (16)
Works for my server. Look for an updated version of GraphQL.NET Server 7 shortly with the new property. It seems that my old notes shown here were actually mostly correct, but it didn't follow through setting the context.User
property to the result.
//-- and you'd think this would work: --
//var auth = context.RequestServices.GetRequiredService<IAuthenticationService>();
//await auth.AuthenticateAsync(context, JwtBearerDefaults.AuthenticationScheme);
See the PR for the proposed implementation. My server code now looks like this, which is a substantial improvement:
endpoints.MapGraphQL(
"/graphql",
opts => {
opts.HandleGet = false;
// GraphQL requests will ignore cookie authentication and use JWT authentication only
opts.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
opts.AuthorizationRequired = true;
opts.AuthorizedRoles.Add("myRole");
})
.RequireCors("GraphQL");
from server.
Which GraphQL nuget packages do you have installed and what versions?
from server.
Is the default authentication scheme the JWT bearer scheme?
from server.
protected virtual void AddAuthentication(IServiceCollection services)
{
var tokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["IdentityServer:TokenValidationParameters:ValidIssuer"],
ValidAudiences = Configuration.GetSection("IdentityServer:TokenValidationParameters:ValidAudiences").Get<List<string>>(),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["IdentityServer:TokenValidationParameters:Secret"])),
NameClaimType = Configuration["IdentityServer:TokenValidationParameters:NameClaimType"],
RoleClaimType = Configuration["IdentityServer:TokenValidationParameters:RoleClaimType"],
};
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
IdentityModelEventSource.ShowPII = true;
services.AddAuthentication().AddJwtBearer(options =>
{
if (JwtBackChannelHandler != null)
{
options.BackchannelHttpHandler = JwtBackChannelHandler;
}
options.Authority = Configuration["IdentityServer:BearerOptions:Authority"];
options.Audience = Configuration["IdentityServer:BearerOptions:Audience"];
options.IncludeErrorDetails = Configuration.GetValue<bool>("IdentityServer:BearerOptions:IncludeErrorDetails");
options.SaveToken = Configuration.GetValue<bool>("IdentityServer:BearerOptions:SaveToken");
options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer:BearerOptions:RequireHttpsMetadata");
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(jwtSecurityTokenHandler);
options.TokenValidationParameters = tokenValidationParameters;
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Path.Value.StartsWith("/hubs/", StringComparison.InvariantCulture) && context.Request.Query.TryGetValue("access_token", out StringValues token))
{
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var te = context.Exception;
logger.Error(te, $"Error during jwt authentication for prinicipal {context.Principal}");
return Task.CompletedTask;
}
};
});
}
that's how jwt authentication is configured (according to https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/7.0/default-authentication-scheme I should not provide an default value anymore?)
installed packages:
<PackageReference Include="Duende.IdentityServer" Version="6.3.2" />
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="6.3.2" />
<PackageReference Include="GraphQL.Client" Version="6.0.0" />
<PackageReference Include="GraphQL.Client.Abstractions" Version="6.0.0" />
<PackageReference Include="GraphQL.Client.LocalExecution" Version="6.0.0" />
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="6.0.0" />
<PackageReference Include="GraphQL.Client.Serializer.SystemTextJson" Version="6.0.0" />
<PackageReference Include="GraphQL.NewtonsoftJson" Version="7.0.0" />
<PackageReference Include="GraphQL.Server.All" Version="7.0.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.0.0" />
yesterday I tried with 7.5 (but same result with 7.0.0 and 7.5.0).
Thanks for your reply!
from server.
played further around, but its not working at all. At the current state I'm always getting successfully authenticated requests - even when no jwt token is provided.
Is there maybe a working example with IdentityServer4 available? I'm confident there is an error on my side, but I'm not able to figure it out.
from server.
What also boggles my mind a bit - in
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Path.Value.StartsWith("/hubs/", StringComparison.InvariantCulture) && context.Request.Query.TryGetValue("access_token", out StringValues token))
{
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var te = context.Exception;
logger.Error(te, $"Error during jwt authentication for prinicipal {context.Principal}");
return Task.CompletedTask;
}
};
you may see how I integrated SignalR into Identity Server. But already that function is not called when a GraphQl query is submitted. So it seems, the whole identity server part is skipped?
from server.
Since I see you're using the GraphQL Server authentication library, and specifically the newer implementation, I'm going to move this to the applicable repo.
from server.
I wrote the authorization implementation you've been trying to use. Basically, the GraphQL middleware uses the identity of the HttpContext.User
property to obtain a ClaimsPrincipal
instance to check authentication and role membership against. This is set by the call to ASP.NET Core's app.UseAuthentication()
which must appear prior to the app.UseGraphQL()
call.
The implementation is very simple (and can easily be overridden with a custom implementation) - see:
With JWT bearer authentication, there's two specific gotchyas that you have to look out for:
First, there is no setting to control the authentication scheme used. I believe that in theory you can have two different authentication schemes, say JWT bearer and cookie, and only one will be default. The default will be used to set HttpContext.User
under normal circumstances. I'm not sure how to tell ASP.NET Core to use a different authentication scheme when using the middleware, which is why I don't have a property to set the auth scheme. It may be that if you use endpoint mapping, you can set the auth scheme of the endpoint within the routing config, and ASP.NET Core will properly set the HttpContext.User
property before the middleware executes. This is similar to how you can program specific CORS rules to take effect.
Second, the JWT bearer claim names differ from those used within C# (assuming of course that you are passing the roles within the token itself). Here's ChatGPT's explanation:
In a JWT (JSON Web Token), the claim name commonly used to represent roles is "roles". The "roles" claim is used to specify the roles or permissions associated with a user.
However, in ASP.NET Core, the claim name for roles is "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". This is the default claim name used by ASP.NET Core's authentication and authorization mechanisms to represent user roles.
It's worth noting that while "roles" is a widely used claim name in JWT, ASP.NET Core follows a more specific claim name to ensure compatibility with other systems and standards.
Normally this remapping is handled automatically by the JWT handler. But I did notice that it appears you've removed the default mappings, and so it's possible that this is an issue.
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
Another issue I've seen is that the [Authorize]
attribute exists within the ASP.NET Core and GraphQL namespaces. When tagging your type-first schema definitions, you need to use the GraphQL.NET AuthorizeAttribute or else it won't work as expected.
I do use JWT bearer extensively for my own servers, but I don't use IdentityServer. Hopefully I can still be of help. I suggest reviewing the JWT sample we have here:
And here is the sample for endpoint middleware:
This is what I would suggest as debugging steps:
- Remove
[Authorize]
attribute from the Query type - Remove role check from the
UseGraphQL
call - Try to connect and perform a simple query such as
{ __typename }
with only theRequireAuthentication
property set - If it works, add test resolver and inspect the
IResolveFieldContext.User
property, checking if the roles were set properly, etc, otherwise: - Ensure that UseAuthorization occurs before UseGraphQL
- Try setting the default authorization scheme; if that works, revert and see if you can configure the authorization scheme when using endpoint routing; if so, set up GraphQL through endpoint routing with a configured authorization scheme
- If all else fails, perhaps you can do something like this (which is what I do in one of my apps):
app.MapWhen(
context => context.Request.Path.Equals(path) &&
(context.WebSockets.IsWebSocketRequest || HttpMethods.IsPost(context.Request.Method) || HttpMethods.IsOptions(context.Request.Method)),
b => {
// use JWT authentication, and ignore other authentication
b.Use(next => (RequestDelegate)(async context => {
//-- you'd think this would work: --
//var user = context.User.Identities.FirstOrDefault(x => x.AuthenticationType == JwtBearerDefaults.AuthenticationScheme);
//context.User = new ClaimsPrincipal(user ?? new ClaimsIdentity());
//-- and you'd think this would work: --
//var auth = context.RequestServices.GetRequiredService<IAuthenticationService>();
//await auth.AuthenticateAsync(context, JwtBearerDefaults.AuthenticationScheme);
//-- but they don't, so we do this --
context.User = new ClaimsPrincipal(new ClaimsIdentity());
var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
var authToken = authHeader?.StartsWith("Bearer ") ?? false ? authHeader[7..] : null;
if (authToken != null) {
var validationParameters = context.RequestServices.GetRequiredService<Microsoft.Extensions.Options.IOptionsMonitor<JwtBearerOptions>>()
.Get(JwtBearerDefaults.AuthenticationScheme)
.TokenValidationParameters;
var handler = new JwtSecurityTokenHandler();
try {
context.User = handler.ValidateToken(authToken, validationParameters, out _);
} catch { }
}
await next(context).ConfigureAwait(false);
}));
// apply GraphQL CORS policy
b.UseCors("GraphQL");
// call graphql
b.UseGraphQL(path, opts => {
opts.AuthorizationRequired = true;
opts.AuthorizedRoles.Add("myRole");
});
});
from server.
If you have any ideas how we can implement an AuthenticationScheme property within the GraphQL middleware, so that ASP.NET Core automatically sets the HttpContext.User
property properly for the JWT Bearer authentication scheme, please let me know. Obviously the above patch is quite specific to my own use case.
from server.
Perhaps this method would work (but I have not tried it):
from server.
just had a quick test right now, looks good! :-) will have a further look for implementation tomorrow. Big big thanks so far!
from server.
Short update, have now my own middleware in place:
protected override ValueTask<bool> HandleAuthorizeAsync(HttpContext context, RequestDelegate next)
{
var resultTask = context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
resultTask.Wait();
var result = resultTask.Result;
if(result.Succeeded)
{
context.User = result.Principal;
}
return base.HandleAuthorizeAsync(context, next);
}
it seems to be working now, not the best of all solutions. What confuses me at the moment
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
is required for me, otherwise the claim cannot be found due to:
if (user.IsInRole(role))
is always looking for the "role" claim, the default InBoundClaimTypeMap is mapping the jwt "role" property to ClaimTypes.Role, which does not match for the "longer" role. But that's probably dug somewhere else within the application (need to check that).
So far its now working (maybe not in the best way - but takes the pressure out for now; will have a look at your described steps later today)
from server.
Excellent! Two comments: HandleAuthorizeAsync is any async method and so you can call AuthenticateAsync with await instead of .Wait and Result for true async flow.
Secondly, if you utilize subscriptions, then you might need to override HandleAuthorizeWebSocketConnectionAsync with basically the same code. I would as a matter of principle even I was not yet using subscriptions.
Perhaps I can add this functionality into GraphQL.NET Server 7.6
from server.
thanks - changed it accordingly. Subscriptions are not yet in use (but will add it there as well)
thanks a lot for your help. As said, will try your steps described above as well - maybe I'll get it to work as well.
from server.
Here is my prototype change (going to test it in my own 'fork' first before making the same PR here):
Shane32/GraphQL.AspNetCore3#59
from server.
Thanks so much for your work and help! Would have taken me ages to figure it out by myself.
from server.
Related Issues (20)
- Using Altair UI while not connected to the internet HOT 3
- How to use plugins in Altair? HOT 6
- 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 8
- 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
- Could not load type 'GraphQL.Server.Transports.AspNetCore.IGraphQLRequestDeserializer' from assembly 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
- .net core 7 AddAuthorizationRule not work HOT 29
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.