7

I am looking for sample code and examples regarding how to implement authorization at resolver function level using GraphQL.NET and ASP.NET CORE 2.

Basically I am trying to prevent the execution of query if the request is not authorized.

Can anyone help me to get some good tutorials or code samples as reference for the implementation.

santosh kumar patro
  • 7,231
  • 22
  • 71
  • 143
  • I am trying to use https://github.com/graphql-dotnet/authorization but it has dependency on claims component. Any help on this is much appreciated. – santosh kumar patro Nov 29 '18 at 11:22
  • Is there any way to enable the use of authorization policies to secure GraphQL types and fields without claims. I want to know how to get the Field and Type details from the request object. – santosh kumar patro Nov 29 '18 at 15:58

2 Answers2

11

For graphql-dotnet/authorization, the page for AspNetCore has not been released, refer Add GraphQL.Server.Authorization.AspNetCore NuGet package #171.

You could implement Authorization.AspNetCore for your own use.

After implement Authorization.AspNetCore, you could configure the Authorize like:

  • Startup.cs

        public class Startup
    {
        public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
        {
            Configuration = configuration;
            Environment = hostingEnvironment;
        }
    
        public IConfiguration Configuration { get; }
        public IHostingEnvironment Environment { get; }
    
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            services.AddAuthentication(option =>
            {
                option.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
            services.AddGraphQL(options =>
            {
                options.EnableMetrics = true;
                options.ExposeExceptions = Environment.IsDevelopment();
    
                //options.
            })
            .AddGraphQLAuthorization(options =>
            {
                options.AddPolicy("Authorized", p => p.RequireAuthenticatedUser());
                //var policy = new AuthorizationPolicyBuilder()
                //                    .
                //options.AddPolicy("Authorized", p => p.RequireClaim(ClaimTypes.Name, "Tom"));
            });
            //.AddUserContextBuilder(context => new GraphQLUserContext { User = context.User });
    
            services.AddSingleton<MessageSchema>();
            services.AddSingleton<MessageQuery>();
    
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }
    
        // 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();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
    
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseAuthentication();
            app.UseGraphQL<MessageSchema>("/graphql");
            app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()
            {
                Path = "/ui/playground"
            });
            app.UseGraphiQLServer(new GraphiQLOptions
            {
                GraphiQLPath = "/ui/graphiql",
                GraphQLEndPoint = "/graphql"
            });
    
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
    
  • Schema

    public class MessageQuery : ObjectGraphType<Message>
    {
        public MessageQuery()
        {
            Field(o => o.Content).Resolve(o => "This is Content").AuthorizeWith("Authorized");
            Field(o => o.SentAt);
            Field(o => o.Sub).Resolve(o => "This is Sub");
        }
    }
    

For complete demo, refer GraphQLNet.

Edward
  • 28,296
  • 11
  • 76
  • 121
  • Thanks Tao for your detailed response. I have a detailed question at https://stackoverflow.com/questions/53580954/implement-authorization-on-fields-using-graphql-net-and-asp-net-core-2 . Any help on this is much appreciated. – santosh kumar patro Dec 03 '18 at 06:21
8

To get GraphQL.Net's authorization to work in ASP.NET Core, first install this package:

GraphQL.Server.Authorization.AspNetCore

In Startup.cs add the following in ConfigureServices. Make sure to add these using statements:

    using GraphQL.Validation;
    using GraphQL.Server.Authorization.AspNetCore;
public void ConfigureServices(IServiceCollection services)
{
    //... other code

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    services
        .AddTransient<IValidationRule, AuthorizationValidationRule>()
        .AddAuthorization(options =>
        {
            options.AddPolicy("LoggedIn", p => p.RequireAuthenticatedUser());
        });

    //... other code
}

Now you'll be able to use AuthorizeWith() at the resolver level to protect the field. Example:

public class MyQuery : ObjectGraphType
{
    public MyQuery(ProductRepository productRepository)
    {
        Field<ListGraphType<ProductType>>(
            "products",
            resolve: context => productRepository.GetAllAsync() 
        ).AuthorizeWith("LoggedIn");
    }
}

You can also protect all queries by adding this.AuthorizeWith() to the top of the Query constructor like this:

 public class MyQuery : ObjectGraphType
 {
     public MyQuery(ProductRepository productRepository)
     {
         this.AuthorizeWith("LoggedIn");
         Field<ListGraphType<ProductType>>(
             "products",
             resolve: context => productRepository.GetAllAsync() 
         );
     }
 }

With that, any unauthenticated access to your GraphQL endpoint will be rejected.

Now in terms of logging someone in, there are many ways to do that. Here's a quick Cookie based authentication example:

Configure cookie based authentication in Startup.cs' ConfigureServices:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(o =>
        {
            o.Cookie.Name = "graph-auth";
        });

Use mutation to log someone in:

public class Session
{
    public bool IsLoggedIn { get; set; }
}

public class SessionType : ObjectGraphType<Session>
{
    public SessionType()
    {
        Field(t => t.IsLoggedIn);
    }
}

public class MyMutation : ObjectGraphType
{
    public MyMutation(IHttpContextAccessor contextAccessor)
    {
        FieldAsync<SessionType>(
            "sessions",
            arguments: new QueryArguments(
                new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "password" }),
            resolve: async context =>
            {
                string password = context.GetArgument<string>("password");

                // NEVER DO THIS...for illustration purpose only! Use a proper credential management system instead. :-)
                if (password != "123")
                    return new Session { IsLoggedIn = false };

                var principal = new ClaimsPrincipal(new ClaimsIdentity("Cookie"));
                await contextAccessor.HttpContext.SignInAsync(principal, new AuthenticationProperties
                {
                    ExpiresUtc = DateTime.UtcNow.AddMonths(6),
                    IsPersistent = true
                });

                return new Session { IsLoggedIn = true };
            });
    }
}
Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275
  • thank you! If we were to add information about the logged in user like the userId, how do we save this information (to the cookie?) and then how do we retrieve it on any other request like example for "products"? – lost in binary Feb 26 '20 at 15:55
  • 1
    @lostinbinary Have a look at the `ClaimsIdentity` contructor overloads. You'll see an option that takes `IEnumerable` and `authenticationType`. Replace my `new ClaimsIdentity("Cookie")` sample code above with something like this: `new ClaimsIdentity(new List{ new Claim("UserId", "some value") }, "Cookie")`. With that, the UserId will be stored inside the cookie. Now to retrieve that value on every request, you can do this: `((ClaimsIdentity)httpContext.User.Identity).ValueFromType("UserId")`. Message me on Twitter if you need more help: https://twitter.com/ojohnnyo – Johnny Oshika Mar 09 '20 at 14:45