- Notifications
You must be signed in to change notification settings - Fork 165
Closed
Description
Hello, the logic I'm using works fine in my API, but when I attempt to implement it in my GraphQL project, it fails to function. Here is the code from my Program.cs:
namespace Koble.GraphQL; using Core; using Core.Authorization; using Core.Authorization.ApiKeyAuthorizationSchema; using Core.Extensions; using Entity; using global::GraphQL; using global::GraphQL.DataLoader; using global::GraphQL.MicrosoftDI; using Microsoft.EntityFrameworkCore; using Stripe; /// <summary> /// GraphQL program. /// </summary> public class Program { /// <summary> /// Main task. /// </summary> /// <param name="args">Main args.</param> public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var configuration = builder.Configuration; // Add Cache for GraphQL. builder.Services.AddDistributedMemoryCache(); // Add GraphQl extra services, like dataloader. builder.Services.AddSingleton<IDataLoaderContextAccessor, DataLoaderContextAccessor>(); builder.Services.AddSingleton<DataLoaderDocumentListener>(); builder.Services.AddSingleton<IDocumentExecuter, DocumentExecuter>(); // Add GraphQL service, and set all the configuration. builder.Services.AddSingleton(services => new Schema(new SelfActivatingServiceProvider(services))) .AddGraphQLUpload() .AddGraphQL(options => options.ConfigureExecution((opt, next) => { opt.EnableMetrics = true; opt.ThrowOnUnhandledException = true; opt.MaxParallelExecutionCount = 100; var services = opt.RequestServices; var listener = services.GetRequiredService<DataLoaderDocumentListener>(); opt.Listeners.Add(listener); return next(opt); }) .AddSystemTextJson() .AddAuthorizationRule()); // Add DbContextFactory for PSQL Koble database. builder.Services.AddDbContextFactory<PsqlKobleContext>( options => options.UseNpgsql(configuration.GetConnectionString("PSQLDB_KOBLE"))); AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); // Add Koble.GraphQL settings (environment variables). builder.Services.ConfigureEnvironmentSettings<Settings>(configuration); builder.Services.Configure<GraphQlSettings>(configuration.GetSection("ConnectionStrings")); builder.Services.Configure<GraphQlSettings>(configuration.GetSection("Google")); builder.Services.Configure<GraphQlSettings>(configuration.GetSection("GraphQLSettings")); builder.Services.Configure<GraphQlSettings>(configuration.GetSection("Sendgrid")); builder.Services.Configure<GraphQlSettings>(configuration.GetSection("Twilio")); // Add Cors configuration. builder.Services.AddCors(options => options.AddDefaultPolicy(policy => { policy .WithOrigins(new string[] { "http://localhost:3000", "http://localhost:3001", "http://localhost:3002", "http://localhost:80", "http://localhost", "http://localhost:5173", }) .AllowAnyHeader() .AllowAnyMethod(); })); // Add authentication and authorization. builder.Services.AddAuthentication("Bearer") .AddOAuth2Introspection(options => { options.Authority = configuration.GetSection("GraphQLSettings")["KOBLE_IDENTITY_URL"]; options.ClientId = "koble_graphql"; options.ClientSecret = configuration.GetSection("GraphQLSettings")["KOBLE_GRAPHQL_SECRET"]; options.EnableCaching = true; options.CacheDuration = TimeSpan.FromSeconds(30); }) .AddScheme<ApiKeyAuthenticationOptions, ApiKeyHandler>("ApiKey", options => { }); builder.Services.AddAuthorization(options => { options.AddPolicy("ApiKeyPolicy", policy => { policy.AddAuthenticationSchemes("ApiKey"); policy.RequireAuthenticatedUser(); }); options.AddUserStudentPolicies(); options.AddUserRecruiterPolicies(); options.AddUserStudentUserRecruiterPolicies(); options.AddApiKeyAuthorizationPolicy(); }); // Add controllers, for SchemaController. builder.Services.AddControllers(); builder.Services.AddHttpContextAccessor(); // Add Stripe configuration. StripeConfiguration.ApiKey = configuration.GetSection("Stripe")["STRIPE_API_KEY"]; var app = builder.Build(); app.UseHttpsRedirection(); app.UseCors(); app.MapControllers(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseGraphQLAltair("/"); app.UseGraphQLUpload<Schema>().UseGraphQL<Schema>(); app.Run(); } }I am attempting to implement the apiKey Policy in this manner:
namespace Koble.GraphQL.Resolvers; using Microsoft.AspNetCore.Http; using global::GraphQL; using global::GraphQL.Types; // TODO: Delete this file after adding the first query extension that use the api key authentication. /// <summary> /// Query extension for initializing the add api key test resolver. /// </summary> public static class QueryAddApiKeyTestExtension { public static void AddAddApiKeyTestResolvers(this Query query) { query.Field<StringGraphType>("addApiKeyTest") .Description("Add api key test.") .AuthorizeWithPolicy("ApiKeyPolicy") .Resolve(context => { var httpContext = context.UserContext as HttpContext; var etst = query.HttpContextAccessor.HttpContext; return httpContext?.Request.Headers["api_key"]; }); } }I have applied this policy in a controller, and it works as expected. The setup in my API's program is quite similar, especially the .AddAuthentication() part, which uses the same code.
Below is the code for my ApiKeyHandler:
namespace Koble.Core.Authorization.ApiKeyAuthorizationSchema; using System.Security.Claims; using System.Text.Encodings.Web; using Entity; using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Utils; /// <summary> /// Defines the koble api key handler. /// </summary> public class ApiKeyHandler : AuthenticationHandler<ApiKeyAuthenticationOptions> { private readonly IDbContextFactory<PsqlKobleContext> psqlKobleContext; /// <summary> /// Initializes a new instance of the <see cref="ApiKeyHandler"/> class. /// </summary> /// <param name="options">Api key options class.</param> /// <param name="logger">Logger instance.</param> /// <param name="encoder">Encode url.</param> /// <param name="clock">System clock.</param> /// <param name="psqlKobleContext">Koble db factory context.</param> public ApiKeyHandler( IOptionsMonitor<ApiKeyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDbContextFactory<PsqlKobleContext> psqlKobleContext) : base(options, logger, encoder, clock) { this.psqlKobleContext = psqlKobleContext; } /// <summary> /// Handle authenticate async. /// </summary> /// <returns>A <see cref="Task{AuthenticationResult}"/> representing the result of the asynchronous operation.</returns> protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { var isHeaderName = this.Request.Headers.TryGetValue(this.Options.HeaderName, out var apiKeyValue); if (!isHeaderName) { return AuthenticateResult.Fail("API key was not provided."); } var apiKey = apiKeyValue.FirstOrDefault(); if (string.IsNullOrWhiteSpace(apiKey)) { return AuthenticateResult.Fail("API key was not provided."); } if (!apiKey.StartsWith(this.Options.ApiKeyPrefix, StringComparison.InvariantCulture)) { return AuthenticateResult.Fail("API key format is not valid, it should start with SLT-."); } var authenticationInfo = await this.GetAuthenticationInfo(apiKey); // If the authenticationInfo is null, then return null. if (authenticationInfo == null) { return AuthenticateResult.Fail("API key is not valid."); } // Create the claims and put them in an identity. var claims = new List<Claim> { new("sub", authenticationInfo.Id.ToString()), new("scope", authenticationInfo.Scope), }; var identity = new ClaimsIdentity(claims, this.Scheme.Name); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, this.Scheme.Name); return AuthenticateResult.Success(ticket); } private async Task<AuthenticationInfo> GetAuthenticationInfo(string apiKey) { await using var context = await this.psqlKobleContext.CreateDbContextAsync(); var integrationService = await context.IntegrationServices .FirstOrDefaultAsync(x => x.ApiKey == CommonMethods.GetSha256(apiKey)); if (integrationService != null) { return new AuthenticationInfo() { Id = integrationService.IntegrationServiceId, Scope = "integration_service", }; } return null; } private class AuthenticationInfo { public Guid Id { get; set; } public string Scope { get; set; } } }Sorry for the inconvenience, the truth is I've been stuck on this for a couple of days, thank you!
Metadata
Metadata
Assignees
Labels
No labels