public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStatusCodePages(async context =>
{
var request = context.HttpContext.Request;
var response = context.HttpContext.Response;
var path = request.Path.Value ?? "";
if (
(response.StatusCode == (int)HttpStatusCode.Unauthorized || response.StatusCode == (int)HttpStatusCode.Forbidden)
&& !request.Headers.ContainsKey("Authorization")
)
{
var redirect = context.HttpContext.AbsoluteURL();
response.Redirect(config["JWT:AuthorizationServiceRedirect"] + config["AppID"] + "?redirectTo=" + redirect);
}
});
}
string redirect = HttpContext.Request.Query["redirectT o"].ToStri ng();
if (!String.IsNullOrEmpty(redirect))
{
Response.Headers.Add("Authorization" , "Bearer " + dto.user.token);
Response.Redirect(redirect);
}
ASKER
I have the protected service which will validate the JWT and only allow the controller to be called if it has a valid JWT.
ASKER
app.UseForwardedHeaders();
2.Do you have some proxy or load-balancer in play? or maybe Kestrel & IIS Combo? If yes, then In your ConfigureServices method, you might have to set ForwardedHeadersOptions (https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.1)Essentially rather than the client being aware of the token and maintaining that just have the services do all the heavy lifting.I had a similar implementation couple of months back and I swore that I will never write my own implementation going forward. :P
ASKER
public class Startup
{
public Startup(IConfiguration configuration)
{
config = configuration;
}
public IConfiguration config { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddBusiness(config);
RsaSecurityKey publicKey;
using (TextReader stream = System.IO.File.OpenText(@"C:\Download\RSAPublicKey.txt"))
{
var reader = new PemReader(stream);
AsymmetricKeyParameter pk = (AsymmetricKeyParameter)reader.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)pk);
publicKey = new RsaSecurityKey(rsaParams);
}
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(token =>
{
token.RequireHttpsMetadata = false;
token.SaveToken = true;
token.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = publicKey,
ValidateIssuer = true,
ValidIssuer = config["JWT:Issuer"],
ValidateAudience = true,
ValidAudience = config["JWT:Audience"],
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(int.Parse(config["JWT:ClockSkewMin"]))
};
});
services.AddAuthorization();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStatusCodePages(async context =>
{
var request = context.HttpContext.Request;
var response = context.HttpContext.Response;
var path = request.Path.Value ?? "";
if (
(response.StatusCode == (int)HttpStatusCode.Unauthorized || response.StatusCode == (int)HttpStatusCode.Forbidden)
&& !request.Headers.ContainsKey("Authorization")
)
{
var redirect = context.HttpContext.AbsoluteURL();
response.Redirect(config["JWT:AuthorizationServiceRedirect"] + config["AppID"] + "?redirectTo=" + redirect);
}
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddBusiness(Configuration);
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
[HttpGet("GetClaims/{Appid}")]
public async Task<ActionResult<UserClaimsDto>> GetClaims(int? AppId)
{
string[] parts = HttpContext.User.Identity.Name.Split(new char[] { '\\' });
string user = parts[parts.Length - 1];
var dto = await Mediator.Send(new GetUserClaimsForLoginQuery() { login = user, AppId = AppId });
string redirect = HttpContext.Request.Query["redirectTo"].ToString();
//attempts at adding the authorization header.
if (!String.IsNullOrEmpty(redirect))
{
Response.Headers.Add("Authorization", "Bearer " + dto.user.token);
Response.Headers.Add("Location", redirect);
//Response.Redirect(redirect)
}
return dto.user;
}
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Company.Core.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using ThirdParty.BouncyCastle.OpenSsl;
public class GetUserClaimsForLoginQuery : IRequest<UserClaimsVm>
{
// any input parameters go here.
public string login { get; set; }
public int? AppId { get; set; }
public class GetUserClaimsForLoginQueryHandler : IRequestHandler<GetUserClaimsForLoginQuery, UserClaimsVm>
{
private readonly IAppSettingsDBContext _context;
private readonly IMapper _mapper;
public GetUserClaimsForLoginQueryHandler(IAppSettingsDBContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<UserClaimsVm> Handle(GetUserClaimsForLoginQuery request, CancellationToken cancellationToken)
{
var user = await _context.Users.Include(u => u.UserGroups).ThenInclude(ug => ug.Group)
.Where(u => u.Username == request.login)
.SingleOrDefaultAsync()
;
string groups = "";
foreach (var ug in user.UserGroups)
{
groups += ug.Group.GroupName;
}
string PrivilegeApp = "";
string Privileges = "";
if (request.AppId != null)
{
// IMPORTANT, DO NOT INJECT PARAMS DIRECTLY INTO THE RAW STRING. THIS WAY IS SAFE AS IT CONVERTS TO DB PARAMS.
var privs = await _context.GetDistinctUserApplicationPrivileges.FromSqlRaw("EXECUTE dbo.GetDistinctUserApplicationPrivileges {0}, {1}", user.UserId, request.AppId).ToListAsync(cancellationToken);
foreach (var priv in privs)
{
Privileges += priv.PrivilegeName + ",";
}
Privileges = Privileges.Trim(',');
if (Privileges.Length > 0)
PrivilegeApp = privs.First().ApplicationName;
}
var now = DateTime.UtcNow;
// generated from
// https://travistidwell.com/jsencrypt/demo/
using (TextReader stream = System.IO.File.OpenText(@"C:\Download\RSAPrivateKey.txt"))
{
var reader = new PemReader(stream);
var key = new RsaSecurityKey(reader.ReadPrivatekey());
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.FirstName),
new Claim(ClaimTypes.GivenName, user.LastName),
new Claim("UserGroups", groups),
new Claim("UserId", user.UserId.ToString()),
new Claim(ClaimTypes.WindowsAccountName, user.Username)
};
// if this is populated, it's for a user and application.
if (!String.IsNullOrEmpty(PrivilegeApp))
claims.Add(new Claim(PrivilegeApp, Privileges));
var token = new SecurityTokenDescriptor()
{
Expires = DateTime.UtcNow.AddMinutes(20),
Issuer = "jwt.company.com",
Audience = "services.company.com",
Subject = new ClaimsIdentity(claims),
Claims = claims.ToDictionary(group => group.Type, group => (object)group.Value),
IssuedAt = now,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256)
};
var tokenHandler = new JwtSecurityTokenHandler();
UserClaimsDto usr = new UserClaimsDto();
usr.token = tokenHandler.CreateEncodedJwt(token);
var vm = new UserClaimsVm
{
user = usr
};
return vm;
}
}
}
}
ASKER
ASKER
doing what I'm talking about but why re-invent the wheel.Exactly. I am done re-inventing the wheel or at least try to not to. I do not have the energy / passion I used to have. Maybe time to look for something else.
ASKER
ASKER
app.UseStatusCodePages(async context =>
{
var request = context.HttpContext.Request;
var response = context.HttpContext.Response;
var path = request.Path.Value ?? "";
if (
(response.StatusCode == (int)HttpStatusCode.Unauthorized || response.StatusCode == (int)HttpStatusCode.Forbidden)
&& !request.Headers.ContainsKey("Authorization")
)
{
var redirect = context.HttpContext.AbsoluteURL();
WebClient c = new WebClient
{
UseDefaultCredentials = true
};
// result is "token:<SignedJWT>"
string[] res = c.DownloadString(config["JWT:AuthorizationServiceRedirect"] + config["AppID"]).Split(":");
string token = "";
if (res.Length > 1)
{
token = res[1];
context.HttpContext.Request.Headers.Add("Authorization", "Bearer " + token);
**** //this still is returning 401 but I have the new token *****
//context.HttpContext.Response.RedirectToAbsoluteUrl(redirect);
}
}
});
ASKER
The .NET Framework is not specific to any one programming language; rather, it includes a library of functions that allows developers to rapidly build applications. Several supported languages include C#, VB.NET, C++ or ASP.NET.
TRUSTED BY
Check this:
https://garywoodfine.com/asp-net-core-2-2-jwt-authentication-tutorial/