I have this return error in Postman when calling post method but project works for get method. Project is a simple Web API with custom basic auth with X-UserID
and Request body check with hmac hash:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-e3ed0f946eea3d3837b9fb1ab1a90264-c22b6030e59a8653-00",
"errors": {
"$": [
"The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
],
"user": [
"The user field is required."
]
}
}
This is the code for the handler:
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
namespace test25_08.Authentication;
public class BasicAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly ApplicationDbContext _context;
public BasicAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger,
UrlEncoder encoder, ISystemClock clock, ApplicationDbContext context) : base(options, logger, encoder, clock)
{
_context = context;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string method = Request.Method;
if (!Request.Headers.ContainsKey("X-UserId"))
{
return AuthenticateResult.Fail("No header found");
}
var headerValue = AuthenticationHeaderValue.Parse(Request.Headers["X-UserId"]);
var bytes = Convert.FromBase64String(headerValue.Parameter);
var credentials = Encoding.UTF8.GetString(bytes);
if (!string.IsNullOrEmpty(credentials))
{
var strings = credentials.Split(":");
var userId = strings[0];
var password = strings[1];
var user = _context.Users
.FirstOrDefault(item =>
item.Id == Convert.ToInt32(userId) && item.Password == password
);
if (user == null)
{
return AuthenticateResult.Fail("No user found");
}
if (method.Equals("POST"))
{
string secret = "secret";
var headerValue2 = AuthenticationHeaderValue.Parse(Request.Headers["X-Digest"]);
if (!Request.Headers.ContainsKey("X-Digest"))
{
return AuthenticateResult.Fail("No header found");
}
Request.EnableBuffering();
Request.Body.Position = 0;
string requestBody = await new StreamReader(Request.Body).ReadToEndAsync();
string replace = Regex.Replace(requestBody, @"\s+", "");
if (!headerValue2.Parameter!.Equals(HashString(replace, secret)))
{
return AuthenticateResult.Fail("Request body doesn't match");
}
}
var claim = new[] { new Claim(ClaimTypes.Name, userId) };
var identity = new ClaimsIdentity(claim, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("UnAuthorized");
}
static string HashString(string stringToHash, string hachKey)
{
UTF8Encoding myEncoder = new UTF8Encoding();
byte[] key = myEncoder.GetBytes(hachKey);
byte[] text = myEncoder.GetBytes(stringToHash);
HMACSHA1 myHmacsha1 = new HMACSHA1(key);
byte[] hashCode = myHmacsha1.ComputeHash(text);
string hash = BitConverter.ToString(hashCode).Replace("-", "");
return hash.ToLower();
}
}
This is configuration in Program.cs
:
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using test25_08;
using test25_08.Authentication;
using test25_08.Service;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
// builder.Services.AddIdentity<User, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>()
// .AddDefaultTokenProviders();
builder.Services.AddScoped<IWalletService, WalletService>();
builder.Services
.AddAuthentication("BasicAuthHandler").AddScheme<AuthenticationSchemeOptions,BasicAuthHandler>("BasicAuthHandler", null);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Can anyone find reason why this issue in Postman?
Updated
If uncheck X-Digest from Postman and commet if (method.Equals("POST")){...}
in handler everything works well.
curl --location --request POST 'https://localhost:44358/api/v1/Users' \
--header 'X-UserId: basic MTphYWE=' \
--header 'X-Digest: basic 707cc304a905d573cd196e2ace1eb565e3c04a82' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": 0,
"userName": "string",
"password": "string",
"fullName": "string",
"passportNumber": "string",
"borNDate": "2022-09-07T03:14:00.985Z",
"isAuthenticated": true
}'