-
Notifications
You must be signed in to change notification settings - Fork 164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for endpoint authorization #158
Add support for endpoint authorization #158
Conversation
Make all IAppBuilders extension methods consistent Add more tests
/// <param name="context"></param> | ||
/// <param name="policyName"></param> | ||
/// <returns>True if the user was authorized and the request should proceed; False if the request should be ended immediately.</returns> | ||
public static async Task<bool> AuthorizeAsync(HttpContext context, string policyName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest modelling this after the [Authorize]
attribute in ASP.NET Core. In that attribute you can pass:
- AuthenticationSchemes - (This is a comma delimeted string but only because you can't pass an array to an attribute). I think this one is important to provide an overload for.
- RoleName - I would recommend using policies but some people like to shoot themselves in the foot and use role names.
- ActiveAuthenticationSchemes - This is obsolete. Do not add this.
Also, is the authorization
repo dead? I literally submitted a PR yesterday to use the ASP.NET Core Authorization package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's best to keep this simple. You can create a policy that specifies the authentication schemes or require role(s). I used a role check for the tests in Transports.AspNetCore.Tests\AuthenticatedTestStartup.cs.
I think the stuff in the authorization repo is more for authorizing individual nodes. I felt there was a separate need to support transport authorization of the endpoint that integrates with ASP.NET Core authentication and will trigger the authentication challenge or forbidden responses.
/// </summary> | ||
/// <param name="builder"></param> | ||
/// <returns></returns> | ||
public static IGraphQLBuilder AddHttpAuthorization(this IGraphQLBuilder builder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the reason this is called AddHttpAuthorization
, because subscriptions don't support auth?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was more to differentiate it from the authorization of the GraphQL nodes.
I also added authorization support for the WebSocket transport. WebSockets still uses HTTP(S) before the connection is upgraded.
{ | ||
await _next(context); | ||
return; | ||
} | ||
|
||
// Authorize request | ||
if (!await HttpAuthorizationHelper.AuthorizeAsync(context, _options.AuthorizationPolicyName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I understand this now. So AuthorizationPolicyName
is like a default policy for the entire API. When you call Microsofts AddAuthorization
function you can already set a default policy there, so I don't think you need this:
services.AddAuthorization(options => options.DefaultPolicy = "FooPolicy");
I think you should take a look at the code from my PR graphql-dotnet/authorization#13 to be able to add auth policies to individual graph fields or graph types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think UseAuthorization()
actually applies the DefaultPolicy
above. So there is already middleware that will achieve this built into ASP.NET Core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know what package UseAuthorization()
is in? I can't find it. I looked for a generic middleware that would do that, but I couldn't find one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed it doesn't exist: https://github.com/aspnet/Security/issues/1734.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're absolutely right, I must have imagined it. Looks like they are planning to add a middleware to do this though. If we can't wait for Microsoft to implement it, perhaps it should be a separate UseDefaultAuthorizationPolicy
middleware.
Either way, we should still not add AuthorizationPolicyName
to GraphQLHttpMiddlewareOptions
. Instead we should use DefaultPolicy
in the authorization options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about using the DefaultPolicy
, but what if you want to use the default policy for MVC and a different policy for your GraphQL endpoint? With the AuthorizationPolicyName
you're able to specify whichever policy you want to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the passing of a policy name vs. using DefaultPolicy
for the reasons @johnrutherford gave.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can run multiple apps in a single ASP.NET Core application with separate DI containers, see blog post. Not well known you can do this.
<ProjectReference Include="..\Core\Core.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit concerned about adding "yet another project" that will have to be packaged as another nuget. FubuMVC ran into this issue of tons of different nuget packages and eventually consolidated everything. Since this is a shared dependency I think it should just be added to the Core project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was unsure since it brought in additional dependencies, but I think the consolidation would be better.
{ | ||
await _next(context); | ||
return; | ||
} | ||
|
||
// Authorize request | ||
if (!await HttpAuthorizationHelper.AuthorizeAsync(context, _options.AuthorizationPolicyName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the passing of a policy name vs. using DefaultPolicy
for the reasons @johnrutherford gave.
|
||
namespace GraphQL.Server.Ui.Voyager | ||
namespace GraphQL.Server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 Like the namespace cleanup.
// Authorize request | ||
if (!await HttpAuthorizationHelper.AuthorizeAsync(context, _options.AuthorizationPolicyName)) | ||
{ | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify here, this doesn't need to call the next Middleware because this auth check failed and it should be calling Forbid
or Challenge
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.
return false; | ||
} | ||
|
||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it reaches here, it appears that we're missing a authorizeResult
case (or it should perhaps never be possible to happen?). Should this throw or set a http status code? Since we're not invoking the next middleware it seems this request will just stop without providing any result.
throw new InvalidOperationException("this should never be reached")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't happen. Yeah, I think it would be a good idea to throw an exception there.
if (!HttpMethods.IsGet(httpRequest.Method) && !HttpMethods.IsPost(httpRequest.Method)) | ||
{ | ||
context.Response.Headers.Add("Allow", "GET, POST"); | ||
context.Response.StatusCode = 405; // Method Not Allowed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be consistent perhaps use HttpStatusCode.MethodNotAllowed
here and cast it to int. That should remove the need for the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recommended approach from MS is now to use Microsoft.AspNetCore.Http.StatusCodes
, as it's a static class with const fields therefore implying more codes can come as opposed to the old HttpStatusCode enum. Don't have a source, they explained it better, was in an aspnet repo though.
@joemcbride pushed some additional changes based on your feedback. |
Is this now obsolete? |
I think there is still a use for it. It allows you to authorize access to the entire endpoint. If the request is not authorized, none of the GraphQL code will be run. If you are relying only on authorization within GraphQL, there is more of a performance cost with the GraphQL execution and checking authorization on nodes. This will also return standard HTTP status codes (i.e., 401 or 403) to indicate to clients that the request was unauthorized/forbidden. This makes it easier for the client to know to re-authenticate the user. Otherwise, the client needs to parse errors in the GraphQL response to look for a specific message. I am currently achieving the same result using a custom AuthorizationMiddleware like this: app.Map("/graphql", branch =>
{
branch.UseAuthorization(Policies.GraphQL);
branch.UseGraphQL<MySchema>("");
}); I would prefer something like this: app.UseGraphQL<MySchema>(opts => opts.AuthorizationPolicyName = Policies.GraphQL); |
@johnrutherford Is this PR still relevant after #440 ? |
I see that at least part of the changes in this PR does what has already been done. Some of the code has already been deprecated and has been removed. The remaining code, as I understand it, establishes certain authorization rules. Authorization is now encapsulated in a separate project. Will you keep working on this PR or is it not worth it? |
@johnrutherford @RehanSaeed friendly bump |
@johnrutherford If there is no more work on this PR, then I'm going to close it after the merging #477 . Otherwise, you need to resolve conflicts and update this PR to target on the |
Closed as stale. |
Make all IAppBuilders extension methods consistent
Add more tests