Request with unsuccessful authorization returns 500 status code
See original GitHub issueIs there an existing issue for this?
- I have searched the existing issues
Describe the bug
When authentication middleware fails to issue authentication ticket but field requires user to be authenticated, 500 http status code returned,
As far as I read, according to GraphQL style, server should return 200 in most cases or 500 if things are really bad (but I didn’t find any official documentation about this). Also, graphql-dotnet returns 200 http status code in case of authorization errors.
Therefore, I expected hotchocolate to return 200 (better this) or at least 401/403 status codes.
Steps to reproduce
I have tried two different variants. First one is authentication with JWT Second one is authentication with custom authentication handler.
JWT authentication
1. Build code
HotChocolateAuthDemo.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HotChocolate.AspNetCore" Version="12.14.0" />
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="12.14.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
</ItemGroup>
</Project>
Properties/launchSettings.json
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"HotChocolateAuthDemo": {
"commandName": "Project",
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5014",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Program.cs
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using HotChocolate.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGraphQLServer().AddQueryType<Query>().AddMutationType<Mutation>().AddAuthorization();
builder.Services.AddSingleton<NotesRepository>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MySuperSecretKey"))
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL();
});
app.Run();
public class Mutation
{
public string ObtainJwt(string userId)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySuperSecretKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new("UserId", userId),
}),
IssuedAt = DateTime.UtcNow,
Expires = DateTime.UtcNow.AddMinutes(2),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);
return jwt;
}
}
public class Query
{
[Authorize]
public NoteViewModel[] GetMyNotes(
[Service] NotesRepository repository,
ClaimsPrincipal user)
{
var userId = user.FindFirstValue("UserId");
var viewModels = repository.Value
.Where(note => note.OwnerId == userId)
.Select(note => note.ToViewModel())
.ToArray();
return viewModels;
}
}
[ObjectType("Note")]
public record NoteViewModel(string Id, string Title, string Body);
public class NotesRepository
{
public readonly NoteDto[] Value = new[]
{
new NoteDto(Id: "1", OwnerId: "1", Title: "user 1 note 1 title", Body: "user 1 note 1 body"),
new NoteDto(Id: "1", OwnerId: "2", Title: "user 2 note 1 title", Body: "user 2 note 1 body")
};
}
public record NoteDto(string Id, string OwnerId, string Title, string Body)
{
public NoteViewModel ToViewModel() => new NoteViewModel(Id, Title, Body);
}
2. Obtain token
mutation {
obtainJwt(userId: "1")
}
3. Execute query with token passed in Authorization
header
Authorization: Bearer {jwt}
{
myNotes {
title
}
}
Custom authentication handler
1. Build code
HotChocolateAuthDemo.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HotChocolate.AspNetCore" Version="12.14.0" />
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="12.14.0" />
</ItemGroup>
</Project>
Properties/launchSettings.json
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"HotChocolateAuthDemo": {
"commandName": "Project",
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5014",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Program.cs
using System.Security.Claims;
using System.Text.Encodings.Web;
using HotChocolate.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGraphQLServer().AddQueryType<Query>().AddAuthorization();
builder.Services.AddSingleton<NotesRepository>();
builder.Services.AddAuthentication(defaultScheme: "UserId")
.AddScheme<AuthenticationSchemeOptions, UserIdAuthHandler>(authenticationScheme: "UserId", _ => { });
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL();
});
app.Run();
public class UserIdAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public UserIdAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{ }
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue("UserId", out var userId))
{
return Task.FromResult(AuthenticateResult.NoResult());
}
var ticket = new AuthenticationTicket(
authenticationScheme: "UserId",
principal: new ClaimsPrincipal(
identity: new ClaimsIdentity(
authenticationType: "UserId",
claims: new []{ new Claim("UserId", userId) }
)));
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
[ObjectType]
public class Query
{
[Authorize]
public NoteViewModel[] GetMyNotes(
[Service] NotesRepository repository,
ClaimsPrincipal user)
{
var userId = user.FindFirstValue("UserId");
var viewModels = repository.Value
.Where(note => note.OwnerId == userId)
.Select(note => note.ToViewModel())
.ToArray();
return viewModels;
}
}
[ObjectType("Note")]
public record NoteViewModel(string Id, string Title, string Body);
public class NotesRepository
{
public readonly NoteDto[] Value = new[]
{
new NoteDto(Id: "1", OwnerId: "1", Title: "user 1 note 1 title", Body: "user 1 note 1 body"),
new NoteDto(Id: "1", OwnerId: "2", Title: "user 2 note 1 title", Body: "user 2 note 1 body")
};
}
public record NoteDto(string Id, string OwnerId, string Title, string Body)
{
public NoteViewModel ToViewModel() => new NoteViewModel(Id, Title, Body);
}
2. Execute query passed UserId
header
UserId: 1
{
myNotes {
title
}
}
Relevant log output
No response
Additional Context?
No response
Product
Hot Chocolate
Version
12.14.0
Issue Analytics
- State:
- Created a year ago
- Reactions:5
- Comments:6 (2 by maintainers)
Top Results From Across the Web
Getting a 500 status code when performing a sign-up request
I am getting an Auth0Exception with message: Request failed with status code 500: Unknown exception whenever I execute a SignUpRequest.
Read more >500 Internal Server Error - HTTP - MDN Web Docs - Mozilla
This error response is a generic "catch-all" response. Usually, this indicates the server cannot find a better 5xx error code to response.
Read more >Axios POST request fails with error status code 500
I'm trying to send a POST request locally with a username and password in the body through Axios. ... HOWEVER using Postman with...
Read more >What is “HTTP 500 Internal Server Error” and How to Fix It?
Error 500, also known as the Internal Server Error, is a common HTTP status code that indicates an issue on the web server's...
Read more >Fixing a 500 internal server error response
A 500 Internal Server Error is an HTTP status code that indicates that the server encountered an unexpected error while processing the request....
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@mac2000 To keep the data when they are present (in the case of a 200 code) it is possible to do this: I think it’s more compliant with the way Hot Chocolate and GraphQL work in general.
Has this been resoluted ? we have the same issue