Skip to content
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

Closed

Conversation

johnrutherford
Copy link
Member

Make all IAppBuilders extension methods consistent
Add more tests

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)
Copy link
Contributor

@RehanSaeed RehanSaeed Aug 17, 2018

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.

Copy link
Member Author

@johnrutherford johnrutherford Aug 17, 2018

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)
Copy link
Contributor

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?

Copy link
Member Author

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))
Copy link
Contributor

@RehanSaeed RehanSaeed Aug 17, 2018

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Contributor

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>
Copy link
Member

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.

Copy link
Member Author

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))
Copy link
Member

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
Copy link
Member

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;
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Member

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")

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Contributor

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.

@johnrutherford
Copy link
Member Author

@joemcbride pushed some additional changes based on your feedback.

@pekkah
Copy link
Collaborator

pekkah commented Oct 19, 2018

Is this now obsolete?

@johnrutherford
Copy link
Member Author

johnrutherford commented Oct 19, 2018

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);

@sungam3r sungam3r added the BREAKING Breaking changes in either public API or runtime behavior label Feb 24, 2020
@Shane32 Shane32 changed the base branch from develop to master October 26, 2020 23:31
@sungam3r
Copy link
Member

@johnrutherford Is this PR still relevant after #440 ?

@sungam3r
Copy link
Member

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?

@sungam3r sungam3r added the needs confirmation The problem is most likely resolved and requires verification by the author label Nov 11, 2020
@sungam3r
Copy link
Member

sungam3r commented Dec 6, 2020

@johnrutherford @RehanSaeed friendly bump

@sungam3r
Copy link
Member

sungam3r commented Dec 20, 2020

@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 develop branch.

@sungam3r
Copy link
Member

Closed as stale.

@sungam3r sungam3r closed this Mar 26, 2021
@sungam3r sungam3r removed the needs confirmation The problem is most likely resolved and requires verification by the author label Mar 26, 2021
@johnrutherford johnrutherford deleted the authorization branch March 29, 2021 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BREAKING Breaking changes in either public API or runtime behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants