SignalR core JWT authentication not working - .net-core

I'm trying to authenticate with JWT in .NET Core 2.2 with SignalR. I don't get an error message that it doesn't work on either the server or the client-side. I have set breakpoints on the server-side where the authentication is supposed to happen. Does anyone have any idea why it's not working? The JWT is built with an asymmetric signature algorithm.
This is how I generate the JWT:
var utcNow = DateTime.UtcNow;
using (RSA privateRsa = RSA.Create())
{
privateRsa.FromXmlFile(Path.Combine(HttpContext.Current.Server.MapPath("~"),
"Keys",
ConfigurationManager.AppSettings["PrivateKey"]
));
var privateKey = new RsaSecurityKey(privateRsa);
SigningCredentials signingCredentials = new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256);
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.ID.ToString()),
//new Claim(JwtRegisteredClaimNames.NameId, user.FullName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, utcNow.ToString())
};
var jwt = new JwtSecurityToken(
signingCredentials: signingCredentials,
claims: claims,
notBefore: utcNow,
expires: utcNow.AddMonths(12),
audience: "https://pacsonweb.com",
issuer: "PACSonWEB3 App"
);
return new JwtSecurityTokenHandler().WriteToken(jwt);
This is how i'm authenticating on the server side :
RsaSecurityKey signingKey = new RsaSecurityKey(publicRsa);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(config =>
{
config.RequireHttpsMetadata = true;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = signingKey,
ValidateAudience = true,
ValidAudience = this.Configuration["Tokens:Audience"],
ValidateIssuer = true,
ValidIssuer = this.Configuration["Tokens:Issuer"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
This is how I send the JWT with SignalR on the client side:
hubConnection = new HubConnectionBuilder().WithUrl(hubUrl, (opts) =>
{
opts.AccessTokenProvider = () => Globals.GetJWTToken();
opts.HttpMessageHandlerFactory = (message) =>
{
if (message is HttpClientHandler clientHandler)
{
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback += CheckCertificate;
clientHandler.CheckCertificateRevocationList = false;
}
return message;
};
}).Build();

Related

SignalR Authentication Identity is always null

I have a WPF app that I am connecting to a SignalR API. However, I am having some issues with my Identity. When I actually call an API Endpoint GET, POST, PUT, or DELETE The Identity is populated correctly (all of the claims are there). When I am connecting to SignalR the Identity Claims are not there.
This is where I register my Jwt token
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["Jwt:Issuer"],
ValidAudience = configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]))
};
options.Authority = configuration["Jwt:Authority"];
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
},
};
});
and here is where I register my connection
_connection = new HubConnectionBuilder()
.ConfigureLogging(logBuilder =>
{
logBuilder.AddConsole();
logBuilder.AddDebug();
})
.WithUrl($"{url}/hubs", options =>
{
options.AccessTokenProvider = _userService.GetToken;//returns Task.FromResult(userToken)
})
.WithAutomaticReconnect(new[]
{
TimeSpan.Zero,
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30),
TimeSpan.FromSeconds(60),
TimeSpan.FromSeconds(120)
})
.Build();
My Jwt object in configuration
"jwt":{
"Key":"some random generated key",
"Issuer":"https://localhost:5001/",
"Audience":"https://localhost:5001/",
"Authority":"https://localhost:5001/"
},
Can you please explain what I am doing wrong here?
I actually found the answer to my own question.
I changed
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
to
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
});
and that did it!
Hope this helps someone else.

Blazor Wasm HttpClient Authorisation header not providing claims

I've upgraded a Blazor Wasm app to .Net 6, by creating new projects and then added MS Identity (not Identity Server) from Chris Sainty's blog. Authorisation looks ok & the user can logon. However the claims are not available to the api controller. This is because it looks like the Authorisation header is not being set for the http client call. If I explicitly set the DefaultRequestHeaders.Authorization to the jwt token then I get the claims in the controller ok. I therefore assume the issue is in how I setup the http client (I thought ApiAuthenticationStateProvider would have done this). Any help would be greatly appreciated.
The relevant parts are:
Client: AuthService:
public async Task<LoginResponse> Login(LoginModel loginModel)
{
loginModel.BaskeyKey = await _localStorageService.GetItemAsync<string>(LocalStorageConstants.BasketKey);
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Login", new StringContent(loginAsJson,
Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<LoginResponse>(
await response.Content.ReadAsStringAsync(),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorageService.SetItemAsync("authToken", loginResult.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", loginResult.Token);
_basketStateService.SetBasket(loginResult.LoginResponseDto.BasketDto);
return loginResult;
}
Server Startup:
builder.Services.AddDefaultIdentity<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<LTDbContext>();
builder.Services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
});
var jwtSettings = builder.Configuration.GetSection("Jwt");
builder.Services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["JwtIssuer"],
ValidAudience = jwtSettings["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(jwtSettings["JwtSecurityKey"]))
};
options.MapInboundClaims = false;
});
and client setup for httpClient (I need an authorised & unauthorised client - I think)
builder.Services.AddHttpClient(AuthConstants.Auth, client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});
builder.Services.AddHttpClient(AuthConstants.NoAuth,
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
builder.Services.AddScoped(sp =>
sp.GetRequiredService<IHttpClientFactory>().CreateClient(AuthConstants.Auth));
and Client - ApiAuthenticationStateProvider
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorageService.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(
new ClaimsIdentity()));
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
}
public void MarkUserAsAuthenticated(string email)
{
var authenticatedUser = new ClaimsPrincipal(
new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, email),
new Claim(ClaimTypes.NameIdentifier, email),
new Claim(ClaimTypes.Email, email)
},
"apiauth"
));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}

Dotnet JWT Bearer TokenValidationParameters issue - Unauthorized? Role(Policy) based authorization doesnt work

I changed the setting in the TokenValidationParameters (ValidateIssuer,ValidateAudience) from False to True.
What the settings used to be (startup) - everything was working perfectly.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
ValidateIssuer = false,
ValidateAudience = false,
};
});
Now every call to the API the requires to authorized stopped working.
The new settings - stopped working
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
ValidateIssuer = true,
ValidateAudience = true,
};
});
Every call to the API that needs to be authorized now is getting unauthorized.
This is my token service:
public class TokenService : ITokenService
{
private readonly SymmetricSecurityKey _key;
private readonly UserManager<AppUser> _userManager;
public TokenService(IConfiguration config, UserManager<AppUser> userManager)
{
this._userManager = userManager;
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"]));
}
public async Task<string> CreateToken(AppUser user)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.NameId, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
};
var roles = await _userManager.GetRolesAsync(user);
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
Does anyone can please explain to me what causes it so I could learn. And how can I fix it ? I tried many ways but unfortunately, nothing seems to work for me.
Thank you so much.
This is the code at GitHub just in case : https://github.com/davidax0204/HeroMain.git
When you set ValidateIssuer and ValidateAudience to true, you should set value for ValidIssuer and ValidAudience when creating JWT and SecurityTokenDescriptor
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(7),
SigningCredentials = creds,
Audience = "YourAudience",
Issuer = "YourIssuer"
};
And adding same value to TokenValidationParameters properties
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"])),
ValidateIssuer = true,
ValidIssuer = "YourIssuer",
ValidateAudience = true,
ValidAudience = "YourAudience"
};
});

JWT Authentication in a distributed scenario with .NET 5

I'm trying to implement JWT authentication in a .NET 5 WebAPI. Everything works fine until I add more web servers to the application.
How could I store JWT, for example, in a SQL Server database instead of having them in memory on the single server?
Here's the implementation working on a single server:
Startup
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Context.JwtIssuer,
ValidAudience = Context.JwtIssuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Context.JwtSecretKey))
};
}
Controller
[HttpGet("Login")]
public IActionResult Login(string uid, string password)
{
var hash = _userManager.GetPasswordHash(uid);
if (string.IsNullOrEmpty(hash))
{
return NotFound("Account not found");
}
if (!_passwordHasher.Check(hash, password))
{
return Unauthorized("Uid/password not correct");
}
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Context.JwtSecretKey));
var token = new JwtSecurityToken(
issuer: Context.JwtIssuer,
audience: Context.JwtIssuer,
expires: DateTime.Now.AddHours(3),
claims: new List<Claim>() { new Claim(ClaimTypes.Name, uid)},
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}

JWT Token not expiring

I'm working on .net core api 2.1, I have implemented JWT token authentication, I want jwt token to expire after given time, but it is not expiring. Token still validation even after expiry time.
Startup.cs code:
// configure jwt authentication
var jwtSettings = jwtSettingsSection.Get<JWTSettings>();
var key = Encoding.ASCII.GetBytes(jwtSettings.SECRET);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ClockSkew = TimeSpan.Zero
};
});
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = true;
//options.ForwardClientCertificate = true;
});
SignIn api code to create token on sign in:
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.SECRET);
var currentTime = DateTime.Now;
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.Now.AddMinutes(2),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
rs.Token = tokenString;
Auth filter to validate token:
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
if (!ValidateToken(filterContext.HttpContext.Request.Headers["TOKEN"]))
{
filterContext.Result = new UnauthorizedResult();
}
}
private bool ValidateToken(string authToken)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = GetValidationParameters();
SecurityToken validatedToken;
IPrincipal principal = tokenHandler.ValidateToken(authToken, validationParameters, out validatedToken);
return true;
}
catch(Exception ex)
{
return false;
}
}
private TokenValidationParameters GetValidationParameters()
{
return new TokenValidationParameters()
{
ValidateLifetime = false, // Because there is expiration in the generated token
ValidateAudience = false, // Because there is no audiance in the generated token
ValidateIssuer = false, // Because there is no issuer in the generated token
//ValidIssuer = _appSettings.ValidIssuer,
//ValidAudience = _appSettings.ValidAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretkey)) // The same key as the one that generate the token
};
}
What can be the issue?

Resources