I am developing a Web API in ASP.NET Core 2.2. I want to authenticate every request by any user at any time based on token stored in Authorization header of HTTP Request so the user can call controllers and actions which are annotated with [AuthorizeAttribute]. This is my middleware:
public class TokenBasedAuthenticationMiddleware
{
private RequestDelegate nextDelegate;
public TokenBasedAuthenticationMiddleware(RequestDelegate next) => nextDelegate = next;
public async Task Invoke(HttpContext httpContext, IRegistrationsRepository registrationsRepository)
{
if (registrationsRepository.IsAuthorized(httpContext.Request.Headers["Authorization"]))
{
//Code To Authenticate this request?
}
await nextDelegate.Invoke(httpContext);
}
}
How can I simply authenticate the request (i.e set HttpContext.User.Identity.IsAuthenticated to true) without going into any complexity?
You can try something like :
var claims = new[] { new Claim("name", "YourName"), new Claim(ClaimTypes.Role, "Admin") };
var identity = new ClaimsIdentity(claims, "JWT");
httpContext.User = new ClaimsPrincipal(identity);
await nextDelegate.Invoke(httpContext);
But you can directly use the AddJwtBearer extension if using JWT bearer token authentication . The JwtBearer middleware looks for tokens (JSON Web Tokens or JWTs) in the HTTP Authorization header of incoming requests. If a valid token is found, the request is authorized. You then add the [Authorize] attribute on your controllers or routes you want protected:
Related code samples :
https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide
https://garywoodfine.com/asp-net-core-2-2-jwt-authentication-tutorial/
Related
I use .NET 6 and add HttpClient in Program.cs like this:
builder.Services.AddHttpClient<IUserClient, UserClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:UserBaseUrl"]);
});
In my onion architecture when I want to create an order a request is coming to OrderService.API and to check userId is correct in IUserService from OrderService.BL I call user microservice API with the help of registered HttpClient. So the problem is that for now, I need to transfer JWT to the business logic layer via method parameters.
var createdOrder = await _orderService.Add(model.MapToDto(), HttpContext.Request.Headers["Authorization"]);
I don't like it because, for every method using the HttpClient, it's necessary to provide an extra parameter. I think maybe there is a way to set up HttpClient default authentication during the current request.
I tried to setup default request headers during HttpClient registration:
builder.Services.AddHttpClient<IUserClient, UserClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:UserBaseUrl"]);
client.DefaultRequestHeaders.Add("Authorization", token); // setup token
});
but I don't know how to get JWT from HttpRequest headers there.
Additionally, I thought maybe I can set up the header for HttpClient in some additional BaseController which would be nested by any other my controller but it doesn't seem to be a great solution.
Maybe there is a way for middleware use but as I understand we handle an incoming request to OrderService and can't handle outcoming requests from HttpClient.
So would be grateful for any of your ideas!
Thanks to #Rena who provided a link to an existing similar problem: https://stackoverflow.com/a/62324677/11398810
So I created a message helper like this and it seems work for me:
public sealed class HttpClientsAuthHelper : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
public HttpClientsAuthHelper(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = _accessor.HttpContext.Request.Headers["Authorization"].First();
request.Headers.Add("Authorization", token);
return await base.SendAsync(request, cancellationToken);
}
}
And added these lines to Program.cs:
builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddTransient<HttpClientsAuthHelper>();
builder.Services.AddHttpClient<IUserClient, UserClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:UserBaseUrl"]);
}).AddHttpMessageHandler<HttpClientsAuthHelper>();
builder.Services.AddHttpClient<IProductClient, ProductClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Clients:ProductBaseUrl"]);
}).AddHttpMessageHandler<HttpClientsAuthHelper>();
I'm not sure how correct such approach so I'll dive a bit into this logic later =)
Currently we have a WPF client app that request bearer tokens to an ASP MVC app using OAuth.
This configuration takes place at startup when an endpoint is defined:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
The client WPF application requests tokens by issuing a POST request to [resourceBaseAddress/Token]. The request content I will assume is not relevante to my question.
The problem is that my "resourceBaseAddress" contains a trailing forward slash, so the request is formed in a way such as http://contoso.com//Token (note the double slash). And it currently works.
But now that we are migrating to ASP.NET Core and by consequence I had to add a controller with (Jwt) Token action, I am experiencing problems to route this request with double forward slashes.
As I cannot change code on client app, how can I configure this controller method properly?
How about a middleware that rewrites the request path? Did you try something like this?
Example:
app.Use(async (context, next) =>
{
var url = context.Request.Path.Value;
if (url.EndsWith("//Token"))
{
// rewrite and continue processing
context.Request.Path = "/Token";
}
await next();
});
app.UseRouting();
And a simple controller like this:
[Route("[controller]")]
public class TokenController : ControllerBase
{
[HttpPost]
public string Post()
{
return "Token";
}
}
I've noticed that many developers subclass the AuthenticationStateProvider both in
Blazor Server App and Blazor WebAssembly App wrongly, and more imprtantly for the wrong
reasons.
How to do it correctly and when ?
First off, you do not subclass the AuthenticationStateProvider for the sole purpose of
adding claims to the ClaimPrincipal object. Generally speaking, claims are added after a
user has been authenticated, and if you need to inspect those claims and tranform them, it
should be done somewhere else, not in the AuthenticationStateProvider object. Incidentally, in
Asp.Net Core there are two ways how you can do that, but this merits a question of its own.
I guess that this code sample led many to believe that this is the place to add claims to the ClaimsPrincipal object.
In the current context, implementing Jwt Token Authentication, claims should be added
to the Jwt Token when it is created on the server, and extracted on the client when required,
as for instance, you need the name of the current user. I've noticed that developers save
the name of the user in the local storage, and retrieved it when needed. This is wrong.
You should extract the name of the user from the Jwt Token.
The following code sample describes how to create a custom AuthenticationStateProvider object
whose objective is to retrieve from the local storage a Jwt Token string that has newly added,
parse its content, and create a ClaimsPrincipal object that is served to interested
parties (subscribers to the AuthenticationStateProvider.AuthenticationStateChanged event)
, such as the CascadingAuthenticationState object.
The following code sample demonstrates how you can implement a custom
authenticationstateprovider properly, and for good reason.
public class TokenServerAuthenticationStateProvider :
AuthenticationStateProvider
{
private readonly IJSRuntime _jsRuntime;
public TokenServerAuthenticationStateProvider(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task<string> GetTokenAsync()
=> await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "authToken");
public async Task SetTokenAsync(string token)
{
if (token == null)
{
await _jsRuntime.InvokeAsync<object>("localStorage.removeItem", "authToken");
}
else
{
await _jsRuntime.InvokeAsync<object>("localStorage.setItem", "authToken", token);
}
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await GetTokenAsync();
var identity = string.IsNullOrEmpty(token)
? new ClaimsIdentity()
: new ClaimsIdentity(ServiceExtensions.ParseClaimsFromJwt(token), "jwt");
return new AuthenticationState(new ClaimsPrincipal(identity));
}
}
And here's a code sample residing in the submit button of a Login page that
calls a Web Api endpoint where the user credentials are validated, after which
a Jwt Token is created and passed back to the calling code:
async Task SubmitCredentials()
{
bool lastLoginFailed;
var httpClient = clientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://localhost:44371/");
var requestJson = JsonSerializer.Serialize(credentials, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "api/user/login")
{
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
});
var stringContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<LoginResult>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
lastLoginFailed = result.Token == null;
if (!lastLoginFailed)
{
// Success! Store token in underlying auth state service
await TokenProvider.SetTokenAsync(result.Token);
NavigationManager.NavigateTo(ReturnUrl);
}
}
Point to note: TokenProvider is an instance of TokenServerAuthenticationStateProvider.
Its name reflects its functionality: handling the recieved Jwt Token, and providing
the Access Token when requested.
This line of code: TokenProvider.SetTokenAsync(result.Token); passes the Jwt Token
to TokenServerAuthenticationStateProvider.SetTokenAsync in which the token is sored
in the local storage, and then raises AuthenticationStateProvider.AuthenticationStateChanged
event by calling NotifyAuthenticationStateChanged, passing an AuthenticationState object
built from the data contained in the stored Jwt Token.
Note that the GetAuthenticationStateAsync method creates a new ClaimsIdentity object from
the parsed Jwt Token. All the claims added to the newly created ClaimsIdentity object
are retrieved from the Jwt Token. I cannot think of a use case where you have to create
a new claim object and add it to the ClaimsPrincipal object.
The following code is executed when an authenticated user is attempting to access
the FecthData page
#code
{
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
var token = await TokenProvider.GetTokenAsync();
var httpClient = clientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://localhost:44371/");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"api/WeatherForecast?startDate={DateTime.Now}"));
var stringContent = await response.Content.ReadAsStringAsync();
forecasts = JsonSerializer.Deserialize<WeatherForecast[]>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
}
Note that the first line of code: var token = await TokenProvider.GetTokenAsync(); retrieves
the Jwt Token stored in the local storage, and add it to the Authorization header of the request.
Hope this helps...
Edit
Note: ServiceExtensions.ParseClaimsFromJwt is a method that gets the Jwt token extracted from the local storage, and parse it into a collection of claims.
Your Startup class should be like this:
public void ConfigureServices(IServiceCollection services)
{
// Code omitted...
services.AddScoped<TokenServerAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<TokenServerAuthenticationStateProvider>());
}
I am using a reference token to get the users claims as the JWT token was too long. This works fine from a client controller:
var introspectionClient = new IntrospectionClient(
"http://localhost:5000/connect/introspect",
"api1",
"secret");
var response = await introspectionClient.SendAsync(
new IntrospectionRequest { Token = await HttpContext.GetTokenAsync("access_token") });
ViewBag.Json = Json(response.Json).Value;
return View("json");
As I can use HttpContext to retrieve the access token and exchange it for the users claims at the introspection endpoint.
However from the authorization handler I cannot access the HttpContext to get the access token and the AuthorizationHandlerContextonly contains claims from the id token.
I'm open to all suggestions but it does feel like there should be a way to get the access token in the authorization handler but i could well be wrong.
Thanks in advance for your time.
I do not know if this is the way you should do it. But to answer your question, you can inject the context like this:
//using Microsoft.AspNetCore.Authentication;
public class MyAuthorizationHandler : AuthorizationHandler<MyRequirement>
{
private IHttpContextAccessor _httpContextAccessor;
public MyAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
}
}
You will need to make HandleRequirementAsync async. You don't have to add IHttpContextAccessor in startup to the services. This is already done by Identity.
I'm trying to implement authentication and authorization in an MVC 5.1 app. The authentication takes place via Facebook that is custom implemented. (I can post that code if needed.) Once FB authenticates and sends back the code and the Authenticate method of the auth service is called to sign the user into the application. There is no auth code in the application itself (thus not using Identity or other membership services).
public async Task<ActionResult> Connect(string code)
{
if (code == null)
{
return RedirectToAction("Index", "Home");
}
else
{
// get access token
var accessToken = await nApplication.FacebookClient.AccessTokenAsync(code);
// get user info from facebook
var meResult = await nApplication.FacebookClient.MeResultAsync(accessToken);
nApplication.NRepository.SaveChanges();
nAuthorization.Authenticate(member);
return RedirectToAction("Index");
}
}
nAuthorization.Authenticate(member); creates a list of claims and executes OWIN SignIn,
claims.Add(new Claim(ClaimTypes.Name, member.Name));
claims.Add(new Claim(ClaimTypes.Role, "Member"));
var claimIdentity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
owinContext.Authentication.SignIn(new AuthenticationProperties { IsPersistent = true }, claimIdentity);
I'm using the Authorize attribute from Mvc namespace. But at this point /Profile/Authenticate/ which is my Owin LoginPath get's called again and again to redirect the user to FB and return to the Connect method above.
[Authorize(Roles = "Member")]
public async Task<ActionResult> Index(int? id)
I've checked the User property in the controller and it is not authenticated. I could set that to a new ClaimsPrincipal but I'd like the auth code to be independent of the HttpContext. And it doesn't seem to be right solution.
My Startup class contains:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = new PathString("/Profile/Authenticate/"),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieSecure = CookieSecureOption.Always,
ReturnUrlParameter = "next"
});
Maybe I am missing something completely fundamental? Any pointers would help, I've looked through articles such as the following but to no avail:
http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/
http://www.khalidabuhakmeh.com/asp-net-mvc-5-authentication-breakdown-part-deux
I think this will solve your problem...it worked for me:
Add an empty method in your global.asax.cs file:
protected void Session_Start()
{
}
for some reason, the asp.net session cookie does not get set at the proper time without this. The Thinktecture devs think this might be happening if your webapp uses http and your identity provider uses https but I have not verified that yet.