Reputation: 112
I am using graphQl Hot Chocolate V11 with .net Core 3.1 I have no problem with the identifying of token expire its just the relaying of that problem back to the requester is the problem.
i am trying to add some Authentication to my Requests but i am having an issue with responding when the authorization token is no longer valid due to the time expiring or even any other potential reason for a token to not be valid for that matter.
but when i throw an exception to try tell the requester that their token has expired it is not returning through the Hot Chocolate IErrorFilter style it comes through as like a server error.
if there is any better built in way to check these things and respond to the requester propely could anybody please help me out? i would morse think the error should be displayed like in the format of the last screenshot i guess as a Hot Chocolate IErrorFilter response (the error in that screenshot is if i dont properly handle when a user is not authenticated seen as i dont have a currentUser to add to the context that the query is expecting)
Upvotes: 0
Views: 1597
Reputation: 377
When a JWT token expires, the default auth error object does not provide a clear or specific error. To address this, I implemented a solution that captures authentication failures during the JWT validation process and saves the error in the HTTP context. Later, a custom error filter inspects GraphQL errors and checks for these saved authentication failures to provide a more specific error message. Here’s how I implemented the solution:
AUTH_NOT_AUTHENTICATED
errors, then check if there was a prior authentication error via HttpContextAccessor. This allows us to provide a specific error message, especially when the token is expired.Program.cs
...
builder.Services.AddHttpContextAccessor();
builder.Services.ConfigureOptions<ConfigureJwtBearerOptions>();
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer();
builder.Services.AddGraphQLServer()
.AddErrorFilter<ErrorFilter>();
...
ConfigureJwtBearerOptions.cs
public class ConfigureJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly JwtAuthOptions _options;
public ConfigureJwtBearerOptions(IOptions<JwtAuthOptions> options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public void Configure(JwtBearerOptions options) => configureJwtBearerOptions(options);
public void Configure(string? name, JwtBearerOptions options) => configureJwtBearerOptions(options);
private void configureJwtBearerOptions(JwtBearerOptions options)
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _options.Issuer,
ValidAudience = _options.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Secret!)),
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// Intercepts JWT auth failure. Save to HTTP context
context.HttpContext.Items["JwtAuthenticationFailure"] = context.Exception;
return Task.CompletedTask;
}
};
}
}
ErrorFilter.cs
public class ErrorFilter : IErrorFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ErrorFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public IError OnError(IError error)
{
// Now we can conditionally show token expired error
if (error.Code == "AUTH_NOT_AUTHENTICATED" &&
_httpContextAccessor.HttpContext?.Items["JwtAuthenticationFailure"] is SecurityTokenExpiredException)
{
return ErrorBuilder.New()
.SetCode("AUTH_TOKEN_EXPIRED")
.SetMessage("Access token already expired")
.Build();
}
return error;
}
}
Related github issue: https://github.com/ChilliCream/graphql-platform/issues/2960
Upvotes: 0
Reputation: 2403
I don't know whether this will fix your issue but it might be an acceptable workaround and have a different outcome. Use this to enforce valid is-logged-in security policy:
// Add policy in Startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("LoggedInPolicy", Policies.LoggedInPolicy);
});
then add a Policies class
public class Policies
{
/// <summary>
/// Requires the user to be logged in
/// </summary>
/// <param name="policy"></param>
public static void MultiFactorAuthenticationPolicy(AuthorizationPolicyBuilder policy)
{
policy.RequireAuthenticatedUser();
}
}
And then decorate the appropriate endpoints with your authorization attribute to apply the policy
/// <summary>
/// Test 'hello world' endpoint
/// </summary>
/// <returns>The current date/time on the server</returns>
[Authorize(Policy = "LoggedInPolicy")]
public string Hello()
{
return DateTime.Now.ToString("O");
}
You may also have to add this to the GraphQL configuration
SchemaBuilder.New()
...
.AddAuthorizeDirectiveType()
...
.Create();
(from https://chillicream.com/docs/hotchocolate/v10/security/, don't know how much of this applies to v11)
That might connect with some different error handlers.
Upvotes: 0
Reputation: 112
The only thing that semi worked was creating my exception like this it allowed me to add a proper error code but still deoesnt return as an answer to the query
throw new GraphQLRequestException(ErrorBuilder.New()
.SetMessage(ExpiredTokenString)
.SetCode(ExpiredTokenCode)
.Build());
Upvotes: 0