Code Monkey home page Code Monkey logo

authorization's Introduction

GraphQL Authorization

License codecov Nuget Nuget GitHub Release Date GitHub commits since latest release (by date) Size

GitHub contributors Activity Activity Activity

A toolset for authorizing access to graph types for GraphQL.NET.

Provides the following packages:

Package Downloads NuGet Latest
GraphQL.Authorization Nuget Nuget

You can get all preview versions from GitHub Packages. Note that GitHub requires authentication to consume the feed. See here.

Usage

  • Register the authorization classes in your DI container - call AddAuthorization on the provided IGraphQLBuilder inside AddGraphQL extension method.
  • Provide the ClaimsPrincipal through ExecutionOptions.User.
  • Add policies to the AuthorizationSettings.
  • Apply a policy to a GraphType or Field - both implement IProvideMetadata:
    • using AuthorizeWithPolicy(string policy) extension method
    • or with AuthorizeAttribute attribute if using Schema + Handler syntax.
  • The AuthorizationValidationRule will run and verify the policies based on the registered policies.
  • You can write your own IAuthorizationRequirement.

Limitations

@skip and @include directives are ignored; all selected fields of the selected operation will be checked for authentication requirements, including referenced fragments. (Other operations in the same document will correctly be skipped.)

This authorization framework only supports policy-based authorization. It does not support role-based authorization, or the [AllowAnonymous] attribute/extension, or the [Authorize] attribute/extension indicating authorization is required but without specifying a policy. It also does not integrate with ASP.NET Core's authorization framework.

The GraphQL.Server repository contains an authorization rule which has the above missing features, intended for use with ASP.NET Core. It may also be tailored with custom authentication code if desired, rather than relying on ASP.NET Core's authentication framework.

Examples

  1. Fully functional basic Console sample.

  2. Fully functional ASP.NET Core sample.

  3. GraphType first syntax - use AuthorizeWithPolicy extension method on IGraphType or IFieldType.

public class MyType : ObjectGraphType
{
    public MyType()
    {
        this.AuthorizeWithPolicy("AdminPolicy");
        Field<StringGraphType>("name").AuthorizeWithPolicy("SomePolicy");
    }
}
  1. Schema first syntax - use AuthorizeAttribute attribute on type, method or property.
[Authorize("MyPolicy")]
public class MutationType
{
    [Authorize("AnotherPolicy")]
    public async Task<string> CreateSomething(MyInput input)
    {
        return await SomeMethodAsync(input);
    }

    [Authorize("SuperPolicy")]
    public string SomeProperty => Guid.NewGuid().ToString();
}

Known Issues

  • It is currently not possible to add a policy to Input objects using Schema first approach.

authorization's People

Contributors

benmccallum avatar cotzo avatar dependabot[bot] avatar dnndevelopernc avatar joemcbride avatar nilzen avatar shane32 avatar slavautesinov avatar sungam3r avatar timowilhelm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

authorization's Issues

Arguments Empty When Trying Authorize

I'm trying to authorize a field by validating it´s input arguments in a custom policy. My policy gets executed correctly but the Arguments property of the AuthorizationContext variable is always empty. What do I need to do in order to get this populated?

Field Definition

            Field<DeviceContextType>(
                name: "deviceContext",
                description: "Get device context data by id.",
                arguments: new QueryArguments(
                    new QueryArgument<IdGraphType> {Name = "id"},
                    new QueryArgument<IdGraphType> {Name = "organizationId"}
                ),
                resolve: context => new DeviceContext
                {
                   // my dummy object here
                }
            ).AuthorizeWith<DeviceQueryPolicy>();

DeviceQueryPolicy

    public class DeviceQueryPolicy : IAuthorizationPolicy
    {
        public DeviceQueryPolicy(IDependencyResolver resolver)
        {
            Requirements = new[]
            {
                new DeviceIdMatchOrganizationIdRequirement(resolver),
            };
        }

        public IEnumerable<IAuthorizationRequirement> Requirements { get; }
    }

DeviceIdMatchOrganizationIdRequirement

        public async Task Authorize(AuthorizationContext context)
        {
            // Arguments is always empty here! Why?
            if (!context.Arguments.ContainsKey("id") 
                || !context.Arguments.ContainsKey("organizationId")) {
                context.ReportError("Organization have no access rights to this device");
                return;
            }

Query

# Passing along two arguments
{
  devices {
    deviceContext(
      id: "26748243-9D9D-4E9A-A890-718A46D2C0D5"
      organizationId: "26748243-9D9D-4E9A-A890-718A46D2C0D5"
    ) {      
      active
    }
  }
}

Authorization depending on property value of returned object

First of all thank you so much for your great work. It's really fun using GraphQL!

Is there on option to authorize an object depending on a property value after getting the object from a databasebase?
In my special case I need to do authorization depending on a resource ownership. The user is allowed to query for objects of a specific type, but only if he is the owner of the resource.
I already played with the leave action of the INodeVisitor "Validate" method, but the AuthorizationContext in IAuthorizationRequirement.Authorize does not provide any Information about the resource.
Am I missing something here?

Confusion with extension methods - looking for a more complete example

I am trying to add authorization logic to an ASP.NET core application utilizing graphql-dotnet with your authorization strategy (field level, type level, etc.).

You reference some examples by pasting in a few functions (which I assume are extensions), such as UseGraphQLWithAuth and AddGraphQLWithAuth. I found in the examples where AddGraphQLWithAuth is defined in authorization/src/Harness/GraphQLAuthExtension, but could not locate UseGraphQLWithAuth. Is that something we would add or is it now baked into the graphql-dotnet/server package?

Also, I've been reading issue 502 (graphql-dotnet/graphql-dotnet#502) and wondering if this information is outdated? Is the intention that we define our own middleware code or is that logic now part of graphql-dotnet server? I ask because I've defined my own middleware that works for handling the graphql request, but (for reasons unknown) does not honor the authorization rules.

I really appreciate the graphql-dotnet stuff - I just wish I could find a a current/complete example showing this stuff in action. Any direction appreciated!

Key files below:

*** ghc.GQLAPI.csproj ***

netcoreapp2.1
<PackageReference Include="graphiql" Version="1.1.0" />
<PackageReference Include="GraphQL" Version="2.3.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="3.2.0" />
<PackageReference Include="GraphQL.Server.Transports.WebSockets" Version="3.2.0" />
<PackageReference Include="GraphQL.Authorization" Version="2.0.27"/>


<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.App"/>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<!-- <PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="3.1.0" />
<PackageReference Include="GraphQL.Server.Transports.WebSockets" Version="3.1.0" />
<PackageReference Include="GraphQL.Server.Ui.GraphiQL" Version="3.1.0" />
<PackageReference Include="GraphQL.Server.Ui.Playground" Version="3.1.0" />
<PackageReference Include="GraphQL.Server.Ui.Voyager" Version="3.1.0" />
 -->
All All

*** Startup.cs ***
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

using AutoMapper;
using GraphQL.Server;
using GraphQL.Http;
using GraphQL;
using GraphQL.Types;

using ghc.GQLAPI.Helpers;
using ghc.Data;
using ghc.Data.Interfaces;
using ghc.Data.Repositories;
using ghc.GQLAPI.Models;
using ghc.GQLAPI.Middleware;
using ghc.Model.Entities;
using GraphQL.Validation;

namespace ghc.GQLAPI
{
public class Startup
{
//TODO: user user secrets to avoid storing salt key in clear text
private IHostingEnvironment _env;
public IConfiguration _configuration { get; }

    public Startup(IConfiguration configuration, IHostingEnvironment env)
    {
        _configuration = configuration;
        _env = env;

        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        builder.AddEnvironmentVariables();
        _configuration = builder.Build();
    }


    // ===DEVELOPMENT 
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureDevelopmentServices(IServiceCollection services)
    {
        // Allow for other classes to access the configuration via dependency injection
        services.AddSingleton<IConfiguration>(_configuration);

        // *** Configure security/identity ****
        // TODO: This is a very weak password strength policy - strengthen for prod
        IdentityBuilder builder = services.AddIdentityCore<User>(opt => 
        {
            opt.Password.RequireDigit = false;
            opt.Password.RequiredLength = 4;
            opt.Password.RequireNonAlphanumeric = false;
            opt.Password.RequireUppercase = false;
        });

        builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
        builder.AddEntityFrameworkStores<GHCOpsDbContext>();  // tell identity system to store security info in the entity framework data store
        builder.AddRoleValidator<RoleValidator<Role>>();
        builder.AddRoleManager<RoleManager<Role>>();
        builder.AddSignInManager<SignInManager<User>>();

        // Add authentication strategy
        var key = Encoding.ASCII.GetBytes(_configuration.GetSection("AppSettings:TokenKey").Value);
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options => {
                options.TokenValidationParameters = new TokenValidationParameters {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });


        // Allow us to reference the data seeding process in the configure method below
        services.AddTransient<Seed>();

        // Specify the DB Context (SqlServer)
        services.AddDbContext<GHCOpsDbContext>(options => 
            options.UseSqlServer(_configuration["ConnectionStrings:GHCOpsDB"],
                b => b.MigrationsAssembly("ghc.Migrations.Development"))
             .ConfigureWarnings( warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning))
             );


        // Add support for cross origin resource support
        services.AddCors();

        // Add support for mapping between domain objects and data transfer objects
        services.AddAutoMapper();


        // BEGIN: GRAPHQL $$$$$$$$$$$$$$$$$$$

        // Extension method to allow for GraphQL Authorization logic
        services.AddGraphQLAuth(options =>
        {
            options.AddPolicy("AdminPolicy", p => p.RequireClaim("role", "Administrator"));
        });

        // Add GraphQL services and configure options
        services.AddGraphQL(options =>
        {
            options.EnableMetrics = true;
            options.ExposeExceptions = _env.IsDevelopment();
        })
        .AddWebSockets() // Add required services for web socket support
        .AddDataLoader() // Add required services for DataLoader support
        .AddUserContextBuilder(httpContext => new GraphQLUserContext { User = httpContext.User });
        
        services.AddSingleton<IDocumentWriter, DocumentWriter>();
        services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
        services.AddSingleton<GHCQuery>();
        services.AddSingleton<GHCMutation>();

        services.AddScoped<IValuesRepository, ValuesRepository>();
        services.AddScoped<IUsersRepository, UsersRepository>();
        services.AddScoped<IClientsRepository, ClientsRepository>();
        services.AddScoped<ICaregiversRepository, CaregiversRepository>();
        services.AddScoped<IKeywordsRepository, KeywordsRepository>();
        services.AddScoped<INotesRepository, NotesRepository>();
        services.AddScoped<IPhotosRepository, PhotosRepository>();

        services.AddSingleton<ghc.GQLAPI.Models.ValueType>();   // had to fully qualify since this conflicts with existing asp.net framework type
        services.AddSingleton<ClientType>();
        services.AddSingleton<UserType>();
        services.AddSingleton<CreateUserInputType>();
        services.AddSingleton<UpdateUserInputType>();
        services.AddSingleton<NoteType>();
        services.AddSingleton<PhotoType>();
        services.AddSingleton<CaregiverType>();
        services.AddSingleton<RecruitingStatusType>();
        services.AddSingleton<ServiceStatusType>();

        // Do note move these two lines above the graphql services
        var sp = services.BuildServiceProvider();
        services.AddSingleton<ISchema>(new GHCSchema(new FuncDependencyResolver(type => sp.GetService(type))));
        // END: GRAPHQL $$$$$$$$$$$$$$$$$$$$$

        // Add Support for Model View Controller Framework
        services.AddMvc(options => 
            {
            var mustBeAuthenticatedPolicy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(mustBeAuthenticatedPolicy));
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddJsonOptions(opt => opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
    }




     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, Seed seeder)
    {
        Console.WriteLine("=========================================");
        Console.WriteLine("Environment: " + (env.EnvironmentName).ToUpper());
        Console.WriteLine("=========================================");
        
        // Ensures migrations have been run or creates the DB from scratch if not found
        UpdateDatabase(app);
        
        if (env.IsDevelopment()) {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else {
            // Establish a global exception handler in case we are not in Development mode
            app.UseExceptionHandler(builder => {
                builder.Run(async context => {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    var error = context.Features.Get<IExceptionHandlerFeature>();
                    if (error != null) {
                        context.Response.AddApplicationError(error.Error.Message);
                        await context.Response.WriteAsync(error.Error.Message);
                    }
                });
            });

         }
        
        // Cross Origin Resource - Order matters - this must come before UseMvc()
        // TODO: Tighten up security here
        app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials());
        
         app.UseWebSockets();         // this is required for websockets support at the ASP.NET CORE level
        app.UseGraphQL<ISchema>("/graphqlmw");
        app.UseGraphiQl("/graphiql");                       // must come before app.UseMvc()!!

        app.UseAuthentication();
        app.UseMvcWithDefaultRoute();
    }



    private static void UpdateDatabase(IApplicationBuilder app)
    {
        using (var serviceScope = app.ApplicationServices
            .GetRequiredService<IServiceScopeFactory>()
            .CreateScope())
        {
            using (var context = serviceScope.ServiceProvider.GetService<GHCOpsDbContext>())
            {
                context.Database.Migrate();
            }
        }
    }
}

}



*** GraphQLAuthExtensions ***

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using GraphQL.Authorization;
using GraphQL.Validation;
using Microsoft.AspNetCore.Builder;
using System.Threading.Tasks;
using ghc.GQLAPI.Middleware;

namespace ghc.GQLAPI.Helpers
{
    public static class GraphQLAuthExtensions
    {
        public static void AddGraphQLAuth(this IServiceCollection services, Action<AuthorizationSettings> configure)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
            services.AddTransient<IValidationRule, AuthorizationValidationRule>();

            services.TryAddSingleton(s =>
            {
                var authSettings = new AuthorizationSettings();
                configure(authSettings);
                return authSettings;
            });

        }
    }
}




*** ValueType.cs with Authorization specs for a simple type***
using ghc.Model.Entities;
using GraphQL.Authorization;
using GraphQL.Types;

namespace ghc.GQLAPI.Models
{
    
    public class ValueType : ObjectGraphType<Value>
    {
        
        public ValueType()
        {
            
            // this.AuthorizeWith("AdminPolicy");     // this can be used to protect the entire type
            Name = "Value";

            Field(x => x.Id).Description("The Value Id");
            Field(x => x.Created).Description("Date & Time item was created");
            Field<StringGraphType>("Name", "This is the name").AuthorizeWith("AdminPolicy");
        }
    }
}



*** GraphQLMiddleware.cs - currently not using since I could not get the code to honor authorization rules ***
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using GraphQL.Types;
using Microsoft.AspNetCore.Authorization;
using Newtonsoft.Json.Linq;
using ghc.GQLAPI.Helpers;
using System.Net;

namespace ghc.GQLAPI.Middleware
{
    // DDD: This middleware is redundant with the graphqlcontroller, not sure if I should use this code or the controller.
    public class GraphQLMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly GraphQLSettings _settings;
        private readonly IDocumentExecuter _executor;
        private readonly IDocumentWriter _writer;

        public GraphQLMiddleware(RequestDelegate next, IDocumentWriter writer, IDocumentExecuter executor, GraphQLSettings settings = null)
        {
            System.Diagnostics.Debugger.Break();
            Console.WriteLine("===================================== INITIALIZING GQL middleware =====================================");
            _next = next;
            _writer = writer;
            _executor = executor;
            _settings = settings;
        }

        public async Task InvokeAsync(HttpContext httpContext, ISchema schema)
        {
            Console.WriteLine("======== Calling GQL middleware");

            if (!IsGraphQLRequest(httpContext))
            {
                await _next(httpContext);
                return;
            } 

            await ExecuteAsync(httpContext, schema);
        }

        private async Task ExecuteAsync(HttpContext httpContext, ISchema schema)
        {
            var request = Deserialize<GraphQLRequest>(httpContext.Request.Body);

            if (request.Query == null) { throw new ArgumentNullException(nameof(request.Query)); }

            var response = await _executor.ExecuteAsync(options =>
            {
                options.Schema = schema;
                options.Query = request.Query;
                options.OperationName = request.OperationName;
                options.Inputs = request.Variables.ToInputs();
                options.UserContext = _settings.BuildUserContext?.Invoke(httpContext);
            }).ConfigureAwait(false);

            if (response.Errors?.Count > 0)
            {
                var errors = WriteErrors(response);
                ExecutionResult executionResult = new ExecutionResult { Errors = errors };
                await WriteResponseAsync(httpContext, executionResult, (int)HttpStatusCode.BadRequest);
            }
            else
            {
                await WriteResponseAsync(httpContext, response, (int)HttpStatusCode.OK);
            }            
        }

        private bool IsGraphQLRequest(HttpContext context)
        {
            return context.Request.Path.StartsWithSegments(_settings.Path) && string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase);
        }

        private async Task WriteResponseAsync(HttpContext httpContext, ExecutionResult result, int httpStatusCode)
        {
            var json = new DocumentWriter(indent: true).Write(result);

            httpContext.Response.StatusCode = httpStatusCode;
            httpContext.Response.ContentType = _settings.ResponseContentType;
            await httpContext.Response.WriteAsync(json);
        }

        private ExecutionErrors WriteErrors(ExecutionResult result)
        {
            var errors = new ExecutionErrors();
            foreach (var error in result.Errors)
            {
                var ex = new ExecutionError(error.Message);
                if (error.InnerException != null)
                {
                    ex = new ExecutionError(error.Message, error.InnerException);
                }
                errors.Add(ex);
            }

            return errors;
        }

        public static T Deserialize<T>(Stream s)
        {
            using (var reader = new StreamReader(s))
            using (var jsonReader = new JsonTextReader(reader))
            {
                var ser = new JsonSerializer();
                return ser.Deserialize<T>(jsonReader);
            }
        }

        private async Task WriteResponseAsync(HttpContext context, ExecutionResult result)
        {
            var json = _writer.Write(result);

            context.Response.ContentType = "application/json";
            context.Response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK;

            await context.Response.WriteAsync(json);
        }


    }

    public class GraphQLRequest
    {
        public string Query { get; set; }
        public string OperationName { get; set; }
        public JObject Variables { get; set; }
    }
}   

Authorizing Subscriptions With JWT

Hi,

I have some authorizations setup to handle Queries and Mutations like so (simplified)

services.AddSingleton(x =>
                {
                    AuthorizationSettings settings = new AuthorizationSettings();
                    settings.AddPolicy(AuthConstants.USERS_POLICY, p => p.RequireClaim(ClaimTypes.Role));
                    settings.AddPolicy(AuthConstants.ADMIN_POLICY, p => p.RequireClaim(ClaimTypes.Role, ((int)UserRoles.Administrator).ToString()));
                    settings.AddPolicy(AuthConstants.SUPERVISOR_POLICY, p => p.RequireClaim(ClaimTypes.Role, ((int)UserRoles.Administrator).ToString(),
                                                                                                             ((int)UserRoles.Supervisor).ToString()));
                    return settings;
                })

Now I'm attempting to add Subscriptions, but it looks like the Authorizations are not working. There didn't seem to be any built-in support for authorizing Subscriptions with JWTs, so I used this class for guidance. I can successfully retrieve the token from the connection, validate it, and add it to the HTTP context in an IOperationMessageListener::BeforeHandleAsync

        public Task BeforeHandleAsync(MessageHandlingContext context)
        {
            if (context.Message.Type == MessageType.GQL_CONNECTION_INIT)
            {
                JObject payload = context.Message.Payload as JObject;
                if (payload.TryGetValue("Authorization", System.StringComparison.OrdinalIgnoreCase, out JToken authValue))
                {
                    string token = authValue.Value<string>();
                    if (string.IsNullOrWhiteSpace(token) == false)
                    {
                        int start = token.IndexOf(BEARER, System.StringComparison.OrdinalIgnoreCase);
                        if (start >= 0)
                        {
                            token = token.Substring(start + BEARER_LENGTH);
                            _httpContextAccessor.HttpContext.User = JwtHelper.CreatePrincipal(token);
                        }
                    }
                }
            }

            ClaimsPrincipal user = _httpContextAccessor.HttpContext.User;
            context.Properties["user"] = user;
            return Task.CompletedTask;
        }

But the subscription endpoint still says that I'm unauthorized when I use AuthorizeWith. Is this a bug or how can I authorize Subscriptions using JWTs? Any guidance would be much appreciated

Allow introspection to skip validation

I have put Authorization on my Root Query, but now "ui playground" cannot get the initial introspection query to work.

I can put the bearer token in UiPlayground to make actual queries, but the documentation on the right doesn't work

is there a way i can allow this to work?

Creating JWT with Role Claims

Hello!

I was searching for examples but couldn't find anything that shows how to create a JWT token that can hold the user's roles. I see some examples like this:
authSettings.AddPolicy("AdminPolicy", _ => _.RequireClaim("role", "Admin"))
Anyway I can't understand how to add that claim into my JWT token. Before I started using GraphQL I was generationg my JWTs with this code:

public string CreateUserToken(User user, Guid companyId)
        {
            DateTime nowUtc = DateTime.UtcNow;
            DateTime expires = nowUtc.AddMinutes(_options.UserTokenLifetime);
            DateTime centuryBegin = new DateTime(1970, 1, 1).ToUniversalTime();
            long exp = (long)(new TimeSpan(expires.Ticks - centuryBegin.Ticks).TotalSeconds);
            long now = (long)(new TimeSpan(nowUtc.Ticks - centuryBegin.Ticks).TotalSeconds);

            var payload = new JwtPayload
            {
                { "sub", user.Id },
                { "iss", _options.Issuer },
                { "iat", now },
                { "exp", exp },
                { "company", companyId },
                { "roles", JsonConvert.SerializeObject(user.GetRolesByCompanyId(companyId)) }
            };
            
            return _jwtSecurityTokenHandler.WriteToken(new JwtSecurityToken(_jwtHeader, payload));
        }

The roles array was automatically recognized by dotnet core and I was able to use the authorization attributes on my controllers. However in graphql this is not supported or I am making a mistake.

I would be grateful if anyone could help me resolve the issue.

NullReferenceException in AuthorizationValidationRule when query/mutation/parameter name is invalid

After registering the authorization classes as follows:

public static void AddGraphQLAuth(this IServiceCollection services)
{
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.TryAddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
    services.AddTransient<IValidationRule, AuthorizationValidationRule>();

    services.TryAddSingleton(s =>
    {
        var authSettings = new AuthorizationSettings();

        authSettings.AddPolicy("AdminPolicy", _ => _.RequireClaim("role", "Admin"));

        return authSettings;
    });
}

When a consumer tries to execute a query or a mutation that doesn't exist or to pass a parameter that doesn't exist:

mutation {
  addDevelper(
    id: "b07b3e4e-ea3c-42fc-9c76-40cbdec25ff5",
    name:"john",
    surname: "smith",
    skills: [ 
        { Name: "C#", Score: 10 }, 
        { Name: "JS", Score: 7 }
        ])
}

The endpoint returns:

{
    "errors": [
        {
            "message": "GraphQL.ExecutionError: Object reference not set to an instance of an object. ---> System.NullReferenceException: Object reference not set to an instance of an object.\r\n   at GraphQL.Authorization.AuthorizationValidationRule.<>c__DisplayClass2_1.<Validate>b__2(ObjectField objectFieldAst)\r\n   at GraphQL.Validation.EnterLeaveListener.GraphQL.Validation.INodeVisitor.Enter(INode node)\r\n   at GraphQL.Validation.BasicVisitor.Visit(INode node)\r\n   at 
[..]
GraphQL.Validation.BasicVisitor.Visit(INode node)\r\n   at GraphQL.Validation.DocumentValidator.Validate(String originalQuery, ISchema schema, Document document, IEnumerable`1 rules, Object userContext, Inputs inputs)\r\n   at GraphQL.DocumentExecuter.ExecuteAsync(ExecutionOptions options)\r\n   --- End of inner exception stack trace ---",
            "extensions": {
                "code": "NULL_REFERENCE"
            }
        }
    ]
}

This only happens if at least one parameter is an object (as the elements of the skills array).

The expected behaviour is that the endpoint returns the same error message it would return if the authorization classes weren't registered:

{
    "errors": [
        {
            "message": "GraphQL.Validation.ValidationError: Cannot query field \"addDevelper\" on type \"Mutation\". Did you mean \"addDeveloper\"?",
            [..]
        }
    ]
}

I don't know the library well enough to understand why context.TypeInfo.GetArgument() is returning null for invalid queries/mutations, but replacing line 40 of AuthorizationValidationRule.cs:

var argumentType = context.TypeInfo.GetArgument().ResolvedType.GetNamedType() as IComplexGraphType;

with

var argumentType = context.TypeInfo.GetArgument()?.ResolvedType.GetNamedType() as IComplexGraphType;

appears to fix the problem.

If the solution is valid, I can create a PR.

Preview Targeting

3.0.39-preview-39 is targeting 3.0.0-preview-1352 instead of 3.0.0-preview-1648.

I'm not able to binding redirect without a public token - is there another work around?

Release cycle

Are there any plans for releasing on a regular basis?
When can we expect a new release ?

FieldBuilder AuthorizeWith not consistent with IProvideMetadata AuthorizeWith

In the AuthorizationMetadataExtensions class, the IProvideMetadata AuthorizeWith extension allows a list of policies; however, the corresponding FieldBuilder AuthorizeWith extension method only allows for a single policy. I believe these should behave identically, each allowing for multiple policies (which all must be true).

I suggest changing:

        public static FieldBuilder<TSourceType, TReturnType> AuthorizeWith<TSourceType, TReturnType>(
            this FieldBuilder<TSourceType, TReturnType> builder, string policy)
        {
            builder.FieldType.AuthorizeWith(policy);
            return builder;
        }

to

        public static FieldBuilder<TSourceType, TReturnType> AuthorizeWith<TSourceType, TReturnType>(
            this FieldBuilder<TSourceType, TReturnType> builder, params string[] policies)
        {
            builder.FieldType.AuthorizeWith(policies);
            return builder;
        }

Authorization ignores claims

using version 3.0.39-preview-39

Startup.cs

  public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            ...
            services.AddGraphQL(new GraphQLOptions()
            {
                EnableMetrics = true,
                ExposeExceptions = true,
            }).AddWebSockets()
            .AddUserContextBuilder(httpContext => {
                return new GraphQLUserContext() { User = httpContext.User };
            });
           ...
        }

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
           ...
            app.UseAuthentication();

            app.UseGraphQLWebSockets<GraphSchema>("/graphql");
            app.UseGraphQL<GraphSchema>("/graphql");

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller}/{action=Index}/{id?}");
            });
         ...
        }

GraphQLAutofacModule.cs

public class GraphQLAutofacModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            ...
            builder.RegisterType<AuthorizationEvaluator>().As<IAuthorizationEvaluator>().SingleInstance();
            builder.RegisterType<AuthorizationValidationRule>().As<IValidationRule>().InstancePerLifetimeScope();
            builder.Register(s =>
            {
                var authSettings = new AuthorizationSettings();
                authSettings.AddPolicy(GraphQLAuthPolicies.Admin.ToString(), y => y.RequireClaim("role", "Admin"));
                authSettings.AddPolicy(GraphQLAuthPolicies.Authenticated.ToString(), y => y.RequireClaim("id"));
                return authSettings;
            }).SingleInstance();
        }
    }

QueryType.cs

 public class QueryType : ObjectGraphType
    {
        
        public QueryType(
             ILifetimeScope scope
            )
        {

            Field<ResultListType<Jurisdiction, JurisdictionType>>(
                "jurisdictions",
                arguments: new QueryArguments(new QueryArgument<NonNullGraphType<JurisdictionQueryInputType>> { Name = "queryObject" }),
                resolve: context =>
                {
                    return scope.Resolve<JurisdictionQueryHandler>().Handle(context.GetArgument<JurisdictionQueryRequest>("queryObject"));
                }).AuthorizeWith(GraphQLAuthPolicies.Authenticated.ToString());
        }
    }

If I remove the AuthroizeWith call at the end of this field and create a breakpoint on the return line the UserContext looks like

image

If I have the AuthroizeWith() call the following error is returned
GraphQL.Validation.ValidationError: You are not authorized to run this query.↵Required claim 'id' is not present.

Not sure what I did wrong.

Require authentication with JWT for only a subset of queries and mutations

I have a use case where I want a subset of my queries and mutations to require authentication with a jwt token and another subset that requires authentication.

I have set up the JWT authentication in startup like this

services.AddAuthentication(options =>
{
	options.DefaultAuthenticateScheme = "JwtTokens";
	options.DefaultChallengeScheme = "JwtTokens";
})
.AddJwtBearer("JwtTokens", jwtBearerOptions =>
{
	jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
	{
		IssuerSigningKeys = new List<RsaSecurityKey>
		{
			  AuthUtils.GetSigningKey(Settings.PublicKey)
		},
		        ValidIssuers = new List<string>{
			Settings.AuthIssuer,
		},
		ValidateIssuerSigningKey = true,
		ValidateIssuer = true,
		ValidateLifetime = true,
		ValidateAudience = false,
		RoleClaimType = "groups",
		ClockSkew = TimeSpan.FromMinutes(5)
	};
});

I then need to put the [Authorize] attribute on my endpoint to read the JWT token, but then I wont get access to the endpoints that I want to be available without authentication.

[HttpPost]
[Authorize]
public Task<IActionResult> Post([FromBody] GraphQLParameter query)
{
	if (query == null) { throw new ArgumentNullException(nameof(query)); }
		return PostInternal(query);
}

Any suggestions on how I can solve this issue?

Correct way to hide schema?

@joemcbride

I only want GraphiQL/introspection look-ups to work if the application is running from localhost - otherwise I want to be able to add an "AuthorizeWith" specifically to that operation.

Otherwise I'd still want everything below that to follow the appropriate policies.
The trouble I'm running it to is the only way I can see to-do that is to stick a policy directly on the "query" graph type, but then it breaks the security free operations.

e.g. I want :

query .. my schema - blocked if not logged in (following policy)
query .. my security free table - allowed.

I have all the code needed/working - it's just this niche case I'm stuck on.

I'm hoping I just missed something and there is simple workaround for this.

Thanks!!!

Auth not working for InputObjectGraphType<>

I'm having some issues with auth between input and output types. With the following types I only got an error if I tried to use the name field in a query, but not in my mutation.

I'm using GraphQL.Authorization 2.0.27 and it seems like this should have been resolved in #17 ?

public class UserType : ObjectGraphType<User>, IGraphQLType
{
	public UserType()
	{
                Name = "User";
		Field(c => c.Id, type: typeof(IdGraphType)).Description("The id of the user");
	        Field(c => c.Name).Description("The name of the user").AuthorizeWith(AuthPolicies.SuperAdminPolicy.Name);
        }
}
public class UserInputType : InputObjectGraphType<User>, IGraphQLType
	{
		public UserInputType()
		{
			Name = "User";
			Field(c => c.Id, type: typeof(IdGraphType)).Description("The id of the user");
			Field(c => c.Name).Description("The name of the user").AuthorizeWith(AuthPolicies.SuperAdminPolicy.Name);
		}
	}

How do I make unauthorized return 401?

I can see the error 400 for "message": "You are not authorized to run this query. Required claim 'role' with any value of 'member' is not present.".

But I need to return 401 http status code, What can I do?

public class MyType : ObjectGraphType
{
	public MyType()
	{
		this.AuthorizeWith("AdminPolicy");
		Field<StringGraphType>("name");
	}
}

'ValidateAsync' in type 'AuthorizationValidationRule' does not have an implementation

GraphQL.Authorization.AuthorizationValidationRule throws the following exception:

Method 'ValidateAsync' in type 'GraphQL.Authorization.AuthorizationValidationRule' from assembly 'GraphQL.Authorization, Version=3.0.38.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

Libs:
GraphQL 3.0.0-preview-1352
GraphQL.Authorization 3.0.38-preview-38

I think the reason is that it tries to implement IValidationRule which comes as follows:

namespace GraphQL.Validation
{
  public interface IValidationRule
  {
    Task<INodeVisitor> ValidateAsync(ValidationContext context);
  }
}

Check authorization of returned type

At present the validation rule doesn’t check the returned GraphType of the Field that is being request, only that the current Field itself is allowed. The returned GraphType may have different authorization policies applied to it.

CheckAuth(fieldAst, fieldDef, userContext, context, operationType);

I believe the fix is to add this additional line:

CheckAuth(fieldAst, fieldDef.ResolvedType, userContext, context, operationType); 

See graphql-dotnet/graphql-dotnet#502 (comment)

How to authorize request for an API-KEY ?

Hello there

How can i authorize both my graph input types and graph object types to only proceed if the request header contains specific header key?

Could you please provide example.

Thank you

Ashish

Feature Request: HttpContext

I'm working in .Net Framework without Identity.

I'm calling "HttpContext.Current" from my "IAuthorizationRequirement" class, which works, but is a bad practice/pattern to follow.

The request would be to include in HttpContext "(AuthorizationContext context)" passed down through from the originally Web API context.

Unable to actually get "Admin" authorization to work using JWT

I have been at it for almost 2 days trying to finalize my use of GraphQL.Authorization to restrict access to queries and mutations intended for "Admin" users. I am using JWT for authentication, and that seems to work. I created a logonUser mutation that returns a token that I subsequently provide in the headers section of the playground like so:

{
    "Authorization":"Bearer [long token string]"
}

This is where things fall apart. I have added the following code to ConfigureServices():

services.AddEntityFrameworkNpgsql()
                .AddDbContext<ApplicationDbContext>();

            services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
            services.AddScoped<IUnitOfWork, ApplicationUnitOfWork>();
            services.AddScoped<IUserRepository, UserRepository>();
            services.AddScoped<IRoleRepository, RoleRepository>();
            services.AddScoped<IBurstIqService, BurstIqService>();
            services.AddScoped<IUserService, UserService>();

            // TODO: Investigate repercussions of this. Does this only make execution serial
            // within a scope or the whole app? Could be a bottleneck going forward.
            services.AddSingleton<IDocumentExecuter, AirDocumentExecutor>();

            services.AddScoped<AppSchema>();
            services.AddScoped<ISchema, AppSchema>();
            services.AddScoped<AppMutation>();
            services.AddScoped<AppQuery>();

            services.AddIdentity<User, Role>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            
            // ===== Add Jwt Authentication ========
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
            services
                .AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    
                })
                .AddJwtBearer(cfg =>
                {
                    cfg.RequireHttpsMetadata = false;
                    cfg.SaveToken = true;
                    cfg.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidIssuer = Configuration["JwtIssuer"],
                        ValidAudience = Configuration["JwtIssuer"],
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
                        ClockSkew = TimeSpan.Zero // remove delay of token when expire
                    };
                });
            
            services.AddAuthorization();

            services.AddGraphQL(o =>
                {
                    o.ExposeExceptions = true;
                    o.EnableMetrics = true;
                })
                .AddUserContextBuilder(context => new GraphQLUserContext { User = context.User })
                .AddGraphTypes(ServiceLifetime.Scoped)
                .AddDataLoader();
            
            services.AddGraphQLAuth((_, s) =>
            {
                _.AddPolicy("AdminPolicy", p => p.RequireClaim("roles", "Admin"));
            });

My GraphQLAuthExtensions class looks like this:

public static class GraphQLAuthExtensions
    {
        public static void AddGraphQLAuth(this IServiceCollection services, Action<AuthorizationSettings, IServiceProvider> configure)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
            services.AddTransient<IValidationRule, AuthorizationValidationRule>();

            services.TryAddTransient(s =>
            {
                var authSettings = new AuthorizationSettings();
                configure(authSettings, s);
                return authSettings;
            });
        }

        public static void AddGraphQLAuth(this IServiceCollection services, Action<AuthorizationSettings> configure)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
            services.AddTransient<IValidationRule, AuthorizationValidationRule>();

            services.TryAddTransient(s =>
            {
                var authSettings = new AuthorizationSettings();
                configure(authSettings);
                return authSettings;
            });
        }
    }

When I enable the following code, the call ALWAYS succeeds no matter if I am logged in or not:

app.UseMiddleware<GraphQLMiddleware>(new GraphQLSettings
            {
                BuildUserContext = ctx => new GraphQLUserContext
                {
                    User = ctx.User
                },
                EnableMetrics = Configuration.GetValue<bool>("EnableMetrics"),
                ExposeExceptions = Configuration.GetValue<bool>("ExposeExceptions")
            });

This uses the same GraphQLMiddleware code in the harness. When I comment the above section out, I always get an error that the user does not have the right roles even though I have passed the appropriate header with a valid token.

I have tried every combination of sample code available--and followed ALL documentation, and I have been unable to get this to work. Roles and users are never detected properly, and the following code for my query doesn't work as intended:

public class AppQuery : ObjectGraphType
    {
        public AppQuery(
            IUserService userService,
            IRoleRepository roleRepository
        )
        {
            Field<ListGraphType<UserType>>("users", resolve: context => userService.Get()).AuthorizeWith("AdminPolicy");
            Field<ListGraphType<RoleType>>("roles", resolve: context => roleRepository.Get());
        }
    }

I have also tried various values for p.RequireClaim call listed above. I have tried "role", "Role", and "roles", thinking this value might be appended to the URL string definition for a role claim.

Can anyone help me get this working? What am I missing?

None of the samples on the web seem to help at all...

Selectively allow anonymous access

Is it possible to white list one or more endpoints?

for example: I would like to require authentication for all mutation and queries except for Login.

Currently we authorize each mutation individually - we fear forgetting one will ship and endpoint with open access. The query mutation we AuthorizeWith at the root level therefore it catches all endpoints.

If this is not possible with the current implementation, I am happy to create a PR but how would this work? Instead of AuthorizeWith on an endpoint have DisableAuthorization() ? This could add metadata that is inspected during evaluation to bypass root level policies. What are the drawbacks to this approach?

Update
Took the initial approach of removing operation level validation and the fields now inherit parent policies if they are not flagged a public.

Example
image

Access User's Claims from Inside a Mutation?

Hello guys!
I am trying to use JWT authentication and now I need to get some info out of my token from inside my mutation. I can use IHttpContextAccesor, get the token from the request header and manually take out what I need. This is kinda not cool so is there a way to access the user's claims from a mutation?

My mutation code:

private async Task<MyModel> Resolve(ResolveFieldContext<object> context)
{
    context.UserContext.Claims ???
}

That UserContext seems empty.

I tried generating my token in 2 ways:

1st way:

public string CreateUserToken(User user, Guid companyId)
        {
            DateTime nowUtc = DateTime.UtcNow;
            DateTime expires = nowUtc.AddMinutes(_options.UserTokenLifetime);
            DateTime centuryBegin = new DateTime(1970, 1, 1).ToUniversalTime();
            long exp = (long)(new TimeSpan(expires.Ticks - centuryBegin.Ticks).TotalSeconds);
            long now = (long)(new TimeSpan(nowUtc.Ticks - centuryBegin.Ticks).TotalSeconds);

            var payload = new JwtPayload
            {
                { "sub", user.Id },
                { "iss", _options.Issuer },
                { "iat", now },
                { "exp", exp },
                { "company", companyId },
                { "roles", JsonConvert.SerializeObject(user.GetRolesByCompanyId(companyId)) }
            };
            
            return _jwtSecurityTokenHandler.WriteToken(new JwtSecurityToken(_jwtHeader, payload));
        }

2nd way:

public string CreateUserToken(User user, Guid companyId)
        {
           var claims = new List<Claim>
            {
                new Claim("user", user.Id.ToString()),
                new Claim("company", companyId.ToString())
            };

            foreach (var role in user.GetRolesByCompanyId(companyId))
            {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }
            
            var tokenDescriptor = new SecurityTokenDescriptor
            {  
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.Now.AddMinutes(Convert.ToInt32(_options.UserTokenLifetime)),  
                Issuer = _options.Issuer ,
                IssuedAt = DateTime.Now            
            };
            
            return _jwtSecurityTokenHandler.WriteToken(_jwtSecurityTokenHandler.CreateToken(tokenDescriptor));
        }

I followed this example project https://github.com/graphql-dotnet/authorization/issues/10 on how to enable authorization.

At this point I don't care about policies and permissions, I just need that "comapnyId" out of my token. I would be grateful if someone could help. Thanks in advance!

Restrict amout of Data

I have the situation, that I have Users that should get a smaller Number of items and others should get the full List. Currently I added two Fields and protect one with a .AuthorizeWith

            Field<ListGraphType<MeasurementType>>(
                "allmeasurements",
                resolve: context =>
                {
                    var dataLoader = dataLoaderContextAccessor.Context.GetOrAddCollectionBatchLoader<string, Measurement>("GetAllMeasurementByDeviceId", GetAllMeasurementByDeviceId);

                    return dataLoader.LoadAsync(context.Source.Id);
                }
            ).AuthorizeWith("UserPolicy");

            Field<ListGraphType<MeasurementType>>(
                "lastmeasurements",
                resolve: context =>
                {
                    var dataLoader = dataLoaderContextAccessor.Context.GetOrAddCollectionBatchLoader<string, Measurement>("GetLastMeasurementByDeviceId", GetLastMeasurementByDeviceId);

                    return dataLoader.LoadAsync(context.Source.Id);
                }
            );

Is there an way to do it better in grapql-dotnet?

Code in ReadMe Doesn't Compile

type not found: GraphQLMiddleware. Also not in the Harness anywhere.

GraphQLSettings defines BuildUserContext with a Task, but the readme code tries to use it as a Task. I fixed by redefining BuildUserContext to use a Task.

Code in Harness looks nothing like Readme code.

Great library. Would love to know how to use it.

Is it possible to use this without graphql-server?

I see the harness makes use of graphql-server. The project I am working on does not, it is a .net 4.6.2 app which implements a graphql controller. Graphql itself works, but I'm a little lost how to set up authorization like this.

I've registered all the services needed, but how do I tell my executor to use the AuthorizationSettings?

        {
            services.AddScoped<IDocumentExecuter, DocumentExecuter>();
            services.AddScoped<Query>();
            foreach (var type in GetGraphQlTypes())
            {
                services.AddSingleton(type);
            }

            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IAuthorizationEvaluator, AuthorizationEvaluator>();
            services.AddTransient<IValidationRule, AuthorizationValidationRule>();
            services.AddTransient<ACLAuthorizationRequirement>();

            services.TryAddSingleton(s =>
            {
                var authSettings = new AuthorizationSettings();

                authSettings.AddPolicy("ACL_TenantPolicy", _ => _.AddRequirement(s.GetService<ACLAuthorizationRequirement>()));

                return authSettings;
            });

            services.AddScoped<ISchema>(sp => new GraphQLSchema(new FuncDependencyResolver(type => sp.GetService(type))));
            EfGraphQLConventions.RegisterInContainer(services, services.BuildServiceProvider().GetService<CommonLobStoreCoreContext>());
        }

It seems authSettings is never used.

FWIW, this is what my controller's method looks like:

        public async Task<IActionResult> Post([FromBody] GraphQLQueryObject query, [FromServices] DataContext dataContext)
        {
            if (query == null) { throw new ArgumentNullException(nameof(query)); }
            var inputs = query.Variables.ToInputs();

            var executionOptions = new ExecutionOptions
            {
                Schema = _schema,
                Query = query.Query,
                Inputs = inputs,
                UserContext = new GraphQLUserContext
                {
                    DataContext = dataContext,
                    User = this.User,
                },
#if (DEBUG)
                ExposeExceptions = true,
                EnableMetrics = true,
#endif
            };

            var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);```

Authorization Not Respected when returning ListGraphType of Authorized Type

For context I have written my own IAuthorizationRequirement. In my case, I have a type that is registered in my schema (MyType) protected with this.AuthorizeWith("MyRegisteredPolicyName") and authorization is working as expected for a query similar to below:

Field<MyType>(
        "myType",
        arguments: new QueryArguments(new QueryArgument<StringGraphType> { Name = "id" }),
        resolve: context =>
        {
          var id = context.GetArgument<string>("id");
          var obj = db.Find(id);
          return obj;
        });

However, when the Query resolves to a list of the same type (ListGraphType<MyType>)

Field<ListGraphType<MyType>>(
        "myTypes",
        resolve: context =>
        {
          var list = db.All();
          return list;
        });

The authorization requirement is not enforced. I was expecting that whenever the type was accessed (directly or indirectly), that the authorization requirements would be enforced. Is this by design?

Require Authorization Without Checking Roles

Hello!

I see that I can define auth policies like this:
authSettings.AddPolicy("AdministratorPolicy", _ => _.RequireClaim(ClaimTypes.Role, "Administrator"));
Then I could use this like:

public class MyType : ObjectGraphType
{
    public MyType()
    {
        this.AuthorizeWith("AdministratorPolicy");
        Field<StringGraphType>("name").AuthorizeWith("SomePolicy");
    }
}

So far so good, but I have parts in my app that require the user to be authenticated but do not require a specific role. Is there anyway I could define a policy that just checks if the user is authorized without specifying required roles?

Something like the [Authorize] attribute in the controllers.

Thanks in advance!

Feature Request: Error Access

In the current version only "ReportError" from "AuthorizationContext context" is available,

Having a pass-up for "rfc.Errors.Add(new ExecutionError" like you can from Resolvers would be helpful.
Particularly I'm looking at wanting to use the optional pass-in "new Dictionary<String, Object>()".

Unable to access Principle using GraphQL.Server 3.5

I have used GraphQL.Server.Transports.AspNetCore v3.5.
In which there is change in AddUserContextBuilder().
Now TUserContext will be IDictionary<string, object> instead of IProvideClaimsPrincipal.
So here need to update AuthorizationValidationRule for aligning it with the Server package.

Here is its Ref: graphql-dotnet/server#218

Not compatible with graphql-dotnet 3.0

In the latest of graphql-dotnet UserContext is expected to be a Dictionary<string, object> and not an object.

This could probably be handled by adding a simple bit:

// var userContext = context.UserContext as IProvideClaimsPrincipal;
IProvideClaimsPrinciple userContext;
if(context.UserContext is Dictionary<string, object>) {
  userContext = (context.UserContext as Dictionary<string, object>)?["user"] as IProvideClaimsPrinciple;
} else {
  userContext = context.UserContext as IProvideClaimsPrinciple;
}

Authorization ignored on Input Arguments

When using AuthorizeWith on the Fields that are used for query, everything works fine but it is ignored when set on arguments that implement InputObjectGraphType. Authorization of fields in the argument is not implemented?

public class AccountInputTypeCreate : InputObjectGraphType<Account>
   {
       public AccountInputTypeCreate()
       {
           Field(x => x.Name);
           Field(x => x.Value, true).AuthorizeWith(ClaimsEnum.ValueAccess.ToString());
       }
   }

Authorization and JWT

Hello,

I would like to know how can one combine JWT and the authorization toolset. The scenario is I would like to provide an endpoint to get a JWT token and then use that token during the graph operations. Please educate me on how to go about it.

Duplication with ASP.NET Core

The following code looked a lot like ASP.NET Core to me but to my surprise it lived in the GraphQL namespace:

var authorizationSettings = new AuthorizationSettings();
authorizationSettings.AddPolicy(
    "AdminPolicy",
    x => x.RequireClaim("role", "admin"));
return authorizationSettings;

The AuthorizationPolicyBuilder in ASP.NET Core has methods for RequireAuthenticatedUser and RequireAssertion which can be very useful. I was considering submitting a PR for these perhaps this project should just be using Microsoft.AspNetCore.Authorization in the first place?

Thoughts @joemcbride?

GraphQLAuthorize attribute not firing using schema first approach

I am working on the qraphQL api and I am trying to use authorization attribute GraphQLAuthorize in GraphQL.Server.Authorization.AspNetCore; I have created policy in startup.cd configureService but not recognized by GraphQLAuthorize attribute.

The [GraphQLAuthorize(Policy = "AUTHORIZED")] is not working. while the AuthorizeWith("AUTHORIZED") is working

using GraphQL.Repository.GraphQL.Types;
using GraphQL.Repository.Repositories;
using GraphQL.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using GraphQL.Server.Authorization.AspNetCore;

namespace GraphQL.Repository.GraphQL
{
    [GraphQLAuthorize(Policy = "AUTHORIZED")]
    public class MenuQuery : ObjectGraphType
    {
        public MenuQuery(MenuRepository menuRepository)
        {
            Field<ListGraphType<NavigationMenuType>>(
                "NavigationMenu",
                arguments: new QueryArguments(new QueryArgument<NonNullGraphType<IdGraphType>> { Name = "applicationId" }),
                resolve: context =>
                {
                    var user = (ClaimsPrincipal)context.UserContext;

                    var applicationId = context.GetArgument<int>("applicationId");
                    return menuRepository.GetNavigationMenus(applicationId);
                });
        }
    }
}

//startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//using GraphQL;
using GraphQL.Server;
using Microsoft.EntityFrameworkCore;
using GraphQL.Repository.Entities;
using GraphQL.Repository.Repositories;
using GraphQL.Repository.GraphQL;
using GraphQL.Repository.GraphQL.Types;
using Microsoft.Extensions.DependencyInjection.Extensions;
using GraphQL.Server.Ui.GraphiQL;
using GraphQL.Server.Ui.Playground;

namespace GraphQL.Api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MenuManagementDevContext>(options =>
              options.UseSqlServer(Configuration["ConnectionStrings:CarvedRockContext"]));

            services.AddScoped<MenuRepository>();
            services.AddScoped<MenuQuery>();

            services.AddScoped<NavigationMenuType>();
            services.AddScoped<RoleNavigationMenuType>();
            services.AddScoped<RoleType>();

            services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
            services.AddScoped<MenuSchema>();

            services.AddGraphQL(o => { o.ExposeExceptions = true; })
                .AddGraphQLAuthorization(options =>
                {
                    options.AddPolicy("AUTHORIZED", p => p.RequireAuthenticatedUser());
                })
                .AddGraphTypes(ServiceLifetime.Scoped).AddUserContextBuilder(httpContext => httpContext.User)
                .AddDataLoader()
                .AddWebSockets();

            services.AddCors();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors(builder =>
                builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

            app.UseWebSockets();
            app.UseGraphQLWebSockets<MenuSchema>("/graphql");
            app.UseGraphQL<MenuSchema>();
            app.UseGraphiQLServer(new GraphiQLOptions());
            app.UseGraphQLPlayground(new GraphQLPlaygroundOptions());


        }
    }
}

Any help would be appreciated!

EnumerableExtensions

Please help
EnumerableExtensions class in present in 2.0 but not available in 2.3.
What is alternate for => ist.Fill(policy);
public static void AuthorizeWith(this IProvideMetadata type, string policy)
{
var list = GetPolicies(type);
list.Fill(policy); // Available in 2.0 but not in 2.3
type.Metadata[PolicyKey] = list;
}

Can I authorize per multiple policies ?

Using the GraphQLAuthorize attribute, can I apply multiple policies at once ...
like ["Admin", "Teacher"] .
Then any user that has any of those claims get authorized.

AuthorizationEvaluator should check authentication state

The first thing in the default AuthorizationEvaluator .Evaluate method should be a check of principal.Identity.IsAuthenticated to conditionally return an AuthenticationResult.Fail() if false since claims are only valid for an authenticated user.

Graphql Authorization not working

I have created the API solution on .Net Core 3.1 with graphql, I am trying to authenticate the graphql endpoint, kindly help me to figure out where I am doing wrong..

Startup.cs code

 public class Startup
    {
        private const string _connectionStringName = "DefaultConnection";
        public IConfiguration Configuration { get; }
        private readonly IWebHostEnvironment _env;
        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            Configuration = configuration;
            _env = env;
        }
        protected virtual void ConfigureDatabase(DbContextOptionsBuilder ctxBuilder)
        {
            ctxBuilder.UseSqlServer(Configuration.GetConnectionString(_connectionStringName));
        }
        protected virtual void AddDbContext(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(ConfigureDatabase);
        }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.Configure<DatabaseConfiguration>(Configuration.GetSection("ConnectionStrings"));
            services.AddIdentity<ApplicationUser, ApplicationRole>() //ApplicationRole
         .AddEntityFrameworkStores<ApplicationDbContext>()
         .AddDefaultTokenProviders();
            services.Configure<IdentityOptions>(options =>
            {
                // Password settings
                options.Password.RequireDigit = true;
                options.Password.RequiredLength = 8;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = true;
                options.Password.RequireLowercase = false;
                options.Password.RequiredUniqueChars = 6;
                // Lockout settings
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
                options.Lockout.MaxFailedAccessAttempts = 10;
                options.Lockout.AllowedForNewUsers = true;
                // User settings
                options.User.RequireUniqueEmail = true;
                options.SignIn.RequireConfirmedEmail = true;
                options.SignIn.RequireConfirmedPhoneNumber = false;
            });
            services.Configure<MongoSettings>(Configuration.GetSection("MongoDbSettings"));
            services.Configure<JwtOptions>(Configuration.GetSection("JwtToken"));
            services.Configure<ApplicationUrl>(Configuration.GetSection("ApplicationUrl"));
            services.AddScoped<IConnectionFactory, ConnectionFactory>();
            services.AddScoped<IEmailRepository, EmailRepository>();
            services.AddScoped<ITokenGenerator, TokenGenerator>();
            services.AddScoped<IAccountRepository, AccountRepository>();
            services.AddGraphQL(o =>
            {
                o.ExposeExceptions = _env.EnvironmentName == "Development";
            })
             .AddGraphTypes(ServiceLifetime.Scoped)
             .AddDataLoader()
             .AddWebSockets();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services
                .AddTransient<IValidationRule, AuthorizationValidationRule>()
                .AddAuthorization(options =>
                {
                    options.AddPolicy(Policies.Admin, Policies.AdminPolicy());
                    options.AddPolicy(Policies.User, Policies.UserPolicy());
                });
            var JwtSection = Configuration.GetSection("JwtToken");
            var JwtOptions = new JwtOptions();
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = JwtOptions.Issuer,
        ValidAudience = JwtOptions.Issuer,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration[JwtOptions.SecretKey])),
        ClockSkew = TimeSpan.Zero
    };
});
            services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
            services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
            services.AddScoped<AppSchema>();
            // If using Kestrel:
            services.Configure<KestrelServerOptions>(options =>
            {
                options.AllowSynchronousIO = true;
            });
            // If using IIS:
            services.Configure<IISServerOptions>(options =>
            {
                options.AllowSynchronousIO = true;
            });
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseGraphQLWebSockets<AppSchema>("/graphql");
            app.UseGraphQL<AppSchema>();        
            app.UseGraphQLPlayground(options: new GraphQLPlaygroundOptions());
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

**GraphQL Query** 


public AppQuery(IAccountRepository accountRepository)
        {
            FieldAsync<ListGraphType<UserMasterType>>("getUserMaster",
                     arguments: new QueryArguments(new List<QueryArgument>
                     {
                new QueryArgument<IntGraphType>
                     {
                        Name = "id"
                     },
                   new QueryArgument<StringGraphType>
                     {
                        Name = "email"
                     },
                    new QueryArgument<StringGraphType>
                     {
                        Name = "userId"
                     },
                     new QueryArgument<IntGraphType>
                     {
                        Name = "userType"
                     },
                     }),
                     resolve: async context =>
                     {
                         var Id = context.GetArgument<int>("id");
                         var Email = context.GetArgument<string>("email");
                         var UserId = context.GetArgument<string>("userId");
                         var UserType = context.GetArgument<int>("userType");
                         return await accountRepository.GetUserMaster(Id, UserId, Email, UserType);
                     }
                 );
            FieldAsync<ListGraphType<RoleMasterType>>("getUserRoles",
                 arguments: new QueryArguments(new List<QueryArgument>
                     {
                   new QueryArgument<IntGraphType>
                     {
                        Name = "roleType"
                     },
                     }),
                    resolve: async context =>
                    {
                        var RoleType = context.GetArgument<int>("roleType");
                        return await accountRepository.GetUserRoles(RoleType);
                    }
                ).AuthorizeWith(Policies.User);
        }

**Code to generate JWT Token**

var tokenResponse = new TokenResponse();
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Email, Email),
                    new Claim(ClaimTypes.Sid, Guid.NewGuid().ToString()),
                    new Claim("role","User")
                };
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecretKey));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                var expires = DateTime.Now.AddMinutes(Convert.ToDouble(_options.ExpiryMinutes));
                var token = new JwtSecurityToken(
                    _options.Issuer,
                    _options.Issuer,
                    claims,
                    expires: expires,
                    signingCredentials: creds
                );
                var AccessToken = new JwtSecurityTokenHandler().WriteToken(token);

Getting the following error

image

Need help with authorization in Middleware

Hey guys,

so I have graphql as backend and React / Apollo as Frontend.
I have allready implemented my JWT Token Auth, which works fine.

Additional to that I have my Middleware, in which the HttpContext is given and the user ist correctly loaded with all Claims:

namespace xxx.Web.GQL.Middleware
{
    public class GraphQLMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IDocumentWriter _writer;
        private readonly IDocumentExecuter _executor;
        private readonly ISchema _schema;

        public GraphQLMiddleware(RequestDelegate next, IDocumentWriter writer, IDocumentExecuter executor, ISchema schema)
        {
            _next = next;
            _writer = writer;
            _executor = executor;
            _schema = schema;
        }

        public async Task InvokeAsync(HttpContext httpContext)
        {
            if (httpContext.Request.Path.StartsWithSegments("/graphql") && string.Equals(httpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
            {
                string body;
                using (var streamReader = new StreamReader(httpContext.Request.Body))
                {
                    body = await streamReader.ReadToEndAsync();

                    var request = JsonConvert.DeserializeObject<GraphQLQuery>(body);

                    var result = await _executor.ExecuteAsync(doc =>
                    {
                        doc.Schema = _schema;
                        doc.Query = request.Query;
                        doc.Inputs = request.Variables.ToInputs();
                        doc.ExposeExceptions = true;
                        doc.UserContext = httpContext.User;
                    }).ConfigureAwait(false);

                    var json = _writer.Write(result);
                    await httpContext.Response.WriteAsync(json);
                }
            }
            else
            {
                await _next(httpContext);
            }
        }
    }
}

Until here it works perfectly fine.

Sadly I am strugling with any futher. I added the GraphQL.Authorization Nuget but all given information are not enough that I could build some working code with it.

What I could do is ofc access the userContext within the resolver of a query and check it "by hand" but I try to avoid it ;)

Field<StringGraphType>(
              name: "hallo",
              resolve: c =>
              {
                  var userPrinc = (ClaimsPrincipal)c.UserContext;
                  var allowed = userPrinc.Claims.Any(x => x.Type == "Role" && x.Value == "Admin" || x.Value == "Mod");
                  if (!allowed)
                  {
                      throw new Exception("TODO: Make this a 401 FORBIDDEN");
                  }
                  return "World";
              }

So what I want is:
Check the claims on Field-Level (for Query or Mutation) for a given Claim with one or more roles in it.

How to extract claims from a token and add to policy.

Hi, I am currently using a ClaimsTransformer class which inherits from IClaimsTransformation and injects custom claims (users/roles) from my db based on the user name i receive from the jwt token. What is the best way to use these custom claims to build a policy?

Thanks!

DI in AuthorizationRequirement

I am struggling to figure out how to inject into an AuthorizationRequirement
I am trying to inject IClient in the following code example.

When setting up the policy, I have to use a constructor which means I am not using DI

                _.AddPolicy(GraphQLPolicyNames.HomeAccess, policy =>
                {
                    policy.AddRequirement(new BasicAccessRequirement());
                    policy.AddRequirement(new HomeAccessRequirement());
                });
    public class HomeAccessRequirement : IAuthorizationRequirement
    {
        private readonly IClient client;
        public HomeAccessRequirement(IClient client)
        {
            this.client = client;
        }
        public Task Authorize(AuthorizationContext context)
        {
            if (AuthorizationHelpers.IsAdminOrSuperAdmin(context.User))
            {
                return Task.FromResult(0);
            }
            var hasId = context.InputVariables.ContainsKey("Id");
            if (hasId)
            {
                dynamic id;
                context.InputVariables.TryGetValue("Id", out id);
                var home = client.GetData(id);
            }

            return Task.FromResult(0);
        }
    }

How can I achieve DI to inject the IClient which is needed for authorization

Full example project?

I've tried to follow the given example code, but I'm not seeing anything happening in my api.

Is there a full example available?

If you could get a very basic one, I'd be more than willing to enhance it.

Not working with GraphQL package >=2.2

When updating to the latest version of the GraphQL package (2.3.0 at the time of raising this issue), I get an exception when calling .AuthorizeWith("my-policy") :

MissingMethodException: Method not found: 'Void GraphQL.EnumerableExtensions.Fill(System.Collections.Generic.IList`1<!!0>, !!0)'.

This seems to be because the GraphQL package removed the EnumerableExtensions.Fill method in version 2.2.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.