Refresh token on asp.net web api and Blazor server side - blazor-server-side

I have an application where the backend is an asp.net web api and the front-end is a Blazor server side. Both projects are using net6.0.
I have implemented jwt token authentication, so users can register and login from the front-end.
My problem is that if the user refreshes a page, he automatically gets logged out. My understanding is that this can be solved using refresh token (I'm not sure if this understanding is correct).
I have tried to follow this guide: Refresh Token with Blazor WebAssembly and ASP.NET Core Web API
However since I'm using Blazor server side I cannot intercept HTTP Requests using the approach in the article.
My question is: in my Blazor server side application how can I prevent users automatically getting logged out due to page refresh and how can I intercept the http request?
UPDATE: Notice I already have everything working in regards to token and authentication between the back and frontend. The part that I'm missing is inside the blazor server side application in the program.cs file. I basically want to intercept all http request and call a method.
In program.cs I have:
builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
I want RefreshTokenService to be called on every http request. I have tried creating a middleware (which calls the RefreshTokenService), inside the program.cs like:
app.UseMyMiddleware();
But this only get called once.

Here's a very simplified version of an API client I'm using in my app that's also split into an ASP.NET Core API backend and a Blazor Server frontend.
The way it works is that the accessToken gets retreived from local storage and added as an authentication header to the HttpRequestMessage in my API client before each API call.
MyApiClient.cs
public class MyApiClient
{
private readonly IHttpClientFactory _clientFactory;
private readonly IMyApiTokenProvider _myApiTokenProvider;
public MyApiClient(IHttpClientFactory clientFactory, IMyApiTokenProvider myApiTokenProvider)
{
_clientFactory = clientFactory;
_myApiTokenProvider = myApiTokenProvider;
}
public async Task<ApiResponse<CustomerListResponse>> GetCustomersAsync()
{
//create HttpClient
var client = _clientFactory.CreateClient("MyApiHttpClient");
//create HttpRequest
var request = CreateRequest(HttpMethod.Get, "/getCustomers");
//call the API
var response = await client.SendAsync(request);
//if Unauthorized, refresh access token and retry
if(response.StatusCode == HttpStatusCode.Unauthorized)
{
var refreshResult = await RefreshAccessToken(client);
if (refreshResult.IsSuccess)
{
//save new token
await _backendTokenProvider.SetAccessToken(refreshResult.NewAccessToken);
//create request again, with new access token
var retryRequest = await CreateRequest(HttpMethod.Get, "/getCustomers");
//retry
response = await client.SendAsync(retryRequest);
}
else
{
//refresh token request failed
return ApiResponse<CustomerListResponse>.Error("Token invalid");
}
}
//parse response
var customers = await response.Content.ReadFromJsonAsync<ApiResponse<CustomerListResponse>>();
return customers;
}
private HttpRequestMessage CreateRequest<TRequest>(string command, HttpMethod method, TRequest requestModel = null) where TRequest : class
{
//create HttpRequest
var request = new HttpRequestMessage(method, command);
//add body if not empty
if (requestModel is not null)
{
request.Content = JsonContent.Create(requestModel);
}
//set the Auth header to the Access Token value taken from Local Storage
var accessToken = await _myApiTokenProvider.GetAccessToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return request;
}
private async Task<ApiResponse<RefreshTokenResponse>> RefreshAccessToken(HttpClient client)
{
var refreshToken = await _backendTokenProvider.GetRefreshToken();
if (refreshToken is null)
{
return ApiResponse<RefreshTokenResponse>.Error("Refresh token is null, cannot refresh access token");
}
var refreshRequest = CreateRequest(HttpMethod.Post, "/refreshToken", new RefreshTokenRequest(refreshToken));
var refreshResponse = await client.SendAsync(refreshRequest);
var refreshResult = await response.Content.ReadFromJsonAsync<ApiResponse<RefreshTokenResponse>>();
return refreshResult;
}
}
MyApiTokenProvider.cs
public class MyApiTokenProvider : IMyApiTokenProvider
{
private readonly ProtectedLocalStorage _protectedLocalStorage;
public MyApiTokenProvider(ProtectedLocalStorage protectedLocalStorage)
{
_protectedLocalStorage = protectedLocalStorage;
}
public async Task<string> GetAccessToken()
{
var result = await _protectedLocalStorage.GetAsync<string>("accessToken");
return result.Success ? result.Value : null;
}
public async Task<string> GetRefreshToken()
{
var result = await _protectedLocalStorage.GetAsync<string>("refreshToken");
return result.Success ? result.Value : null;
}
public async Task SetAccessToken(string newAccessToken)
{
await _protectedLocalStorage.SetAsync("accessToken", newAccessToken);
}
public async Task SetRefreshToken(string newRefreshToken)
{
await _protectedLocalStorage.SetAsync("refreshToken", newRefreshToken);
}
}

Related

Error in ASP.NET Core MVC and Web API project

I have an ASP.NET Core MVC and also Web API project.
This error occurs when I try to send project information to the API (of course API works fine and I do not think there is a problem):
UnsupportedMediaTypeException: No MediaTypeFormatter is available to read a "TokenModel" object of "text / plain" media content.
My code is:
public class TokenModel
{
public string Token { get; set; }
}
and in AuthController I have:
var _Client = _httpClientFactory.CreateClient("MyApiClient");
var jsonBody = JsonConvert.SerializeObject(login);
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var response = _Client.PostAsync("/Api/Authentication", content).Result;
if (response.IsSuccessStatusCode)
{
var token = response.Content.ReadAsAsync<TokenModel>().Result;
}
The error occurs on this line:
var token = response.Content.ReadAsAsync<TokenModel>().Result;
HomeController:
public IActionResult Index()
{
var token = User.FindFirst("AccessToken").Value;
return View(_user.GetAllUsers(token));
}
UserRepository:
public List<UserViewModel> GetAllUsers(string token)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var res = _client.GetStringAsync(UrlMyApi).Result;
List<UserViewModel> users = JsonConvert.DeserializeObject<List<UserViewModel>>(res);
return users;
}
Your API is returning content-type of text/plain and none of the default media type formatters(MediaTypeFormatter) which ReadAsAsync<string>() will try to use support parsing it as is. They work with JSON/XML. You can go a couple of ways but maybe the easiest is to read the content as string and deserialize it after:
var tokenJSON = response.Content.ReadAsStringAsync().Result;
var token = JsonConvert.DeserializeObject<TokenModel>(tokenJSON);
Also, as you're using the Async methods, you should be returning Task from your actions and await the result instead of using .Result as you're just creating overhead currently.
var tokenJSON = await response.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<TokenModel>(tokenJSON);

How to add JWT Bearer Token to ALL requests to an API

I'm in the process of trying to put together a small project which uses Asp.Net Core Identity, Identity Server 4 and a Web API project.
I've got my MVC project authenticating correctly with IdS4 from which I get a JWT which I can then add to the header of a request to my Web API project, this all works as expected.
The issue I have is how I'm actually adding the token to the HttpClient, basically I'm setting it up for every request which is obviously wrong otherwise I'd have seen other examples online, but I haven't been able to determine a good way to refactor this. I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!
Here is an example MVC action that calls my API:
[HttpGet]
[Authorize]
public async Task<IActionResult> GetFromApi()
{
var client = await GetHttpClient();
string testUri = "https://localhost:44308/api/TestItems";
var response = await client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
var data = await response.Content.ReadAsStringAsync();
GetFromApiViewModel vm = new GetFromApiViewModel()
{
Output = data
};
return View(vm);
}
And here is the GetHttpClient() method which I call (currently residing in the same controller):
private async Task<HttpClient> GetHttpClient()
{
var client = new HttpClient();
var expat = HttpContext.GetTokenAsync("expires_at").Result;
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
//SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
}
var accessToken = await HttpContext.GetTokenAsync("access_token");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return client;
}
My StartUp classes are pretty standard from what I gather, but if they could be useful, then I'll add them in.
I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!
The problem is that the docs are really spread all over, so it's hard to get a big picture of all the best practices. I'm planning a blog series on "Modern HTTP API Clients" that will collect all these best practices.
First, I recommend you use HttpClientFactory rather than new-ing up an HttpClient.
Next, adding an authorization header is IMO best done by hooking into the HttpClient's pipeline of message handlers. A basic bearer-token authentication helper could look like this:
public sealed class BackendApiAuthenticationHttpClientHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
public BackendApiAuthenticationHttpClientHandler(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var expat = await _accessor.HttpContext.GetTokenAsync("expires_at");
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
//SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
}
var token = await _accessor.HttpContext.GetTokenAsync("access_token");
// Use the token to make the call.
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
This can be hooked up via DI:
services.AddTransient<BackendApiAuthenticationHttpClientHandler>();
services.AddHttpClient<MyController>()
.ConfigureHttpClient((provider, c) => c.BaseAddress = new Uri("https://localhost:44308/api"))
.AddHttpMessageHandler<BackendApiAuthenticationHttpClientHandler>();
Then you can inject an HttpClient into your MyController, and it will magically use the auth tokens:
// _client is an HttpClient, initialized in the constructor
string testUri = "TestItems";
var response = await _client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
var data = await response.Content.ReadAsStringAsync();
GetFromApiViewModel vm = new GetFromApiViewModel()
{
Output = data
};
return View(vm);
This pattern seems complex at first, but it separates the "how do I call this API" logic from "what is this action doing" logic. And it's easier to extend with retries / circuit breakers / etc, via Polly.
You can use HttpRequestMessage
// Create this instance once on stratup
// (preferably you want to keep an instance per base url to avoid waiting for socket fin)
HttpClient client = new HttpClient();
Then create an instance of HttpRequestMessage:
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Get,
"https://localhost:44308/api/TestItems");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "ey..");
await client.SendAsync(request);

ASP.NET Core 3.1 - PostAsync/PostAsJsonAsync method in Integration Test always returns Bad Request

This is my register method inside the AuthController.
[HttpPost(ApiRoutes.Auth.Register)]
public async Task<IActionResult> Register(UserRegistrationRequest request)
{
var authResponse = await _authService.RegisterAsync(request.Email, request.Password);
if (!authResponse.Success)
{
return BadRequest(new AuthFailedResponse
{
Errors = authResponse.Errors
});
}
return Ok(new AuthSuccessResponse
{
Token = authResponse.Token,
RefreshToken = authResponse.RefreshToken
});
}
I'm trying to call this method by using TestClient.PostAsync() method, unfortunately it always returns Bad Request. I've already tried calling the TestClient.PostAsJsonAsync(ApiRoutes.Auth.Register, user) method by importing Microsoft.AspNet.WebApi.Client package, the result is the same.
var user = new UserRegistrationRequest
{
Email = "user1#testtest.com",
Password = "P#ssw0rd1!!!!!"
};
var response = await TestClient.PostAsync(
ApiRoutes.Auth.Register,
new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8)
{
Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
});
You are missing the FromBody attribute from you action parameter. When you are sending json data to a controller that will be part of the request body. You can tell to the controller how to bind the incoming data, in your case from the body. So you code should look like:
public async Task<IActionResult> Register([FromBody]UserRegistrationRequest request)
{
…
}
You could read more about bindings in the official documentation.

Sustainsys Saml2 Handler AuthenticateAsync() method operation is not implemented

I'm trying a simple implementation in my Asp net Core application of Saml2 to integrate with an Ad FS server. I can't figure why I am getting this error. I downloaded the samples from the gitHub and tried to adapt it in my application.
NotImplementedException: The method or operation is not implemented.
Sustainsys.Saml2.AspNetCore2.Saml2Handler.AuthenticateAsync()
Here's my implementation, my application is running on Asp Net Core
On StartUp
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = Saml2Defaults.Scheme;
})
.AddSaml2(options =>
{
options.SPOptions.EntityId = new EntityId("http://myAdfsServer.myDomain.com/adfs/services/trust");
options.SPOptions.ReturnUrl = new Uri("https://localhost:5000");
options.IdentityProviders.Add(
new IdentityProvider(new EntityId("http://myAdfsServer.myDomain.com/adfs/services/trust"), options.SPOptions)
{
LoadMetadata = true,
MetadataLocation = "https://myAdfsServer.myDomain.com/FederationMetadata/2007-06/FederationMetadata.xml"
//MetadataLocation = "FederationMetadata.xml"
});
//options.SPOptions.ServiceCertificates.Add(new X509Certificate2(certificate.ToString()));
})
.AddCookie();
On my Controller
trying something similar to Sustainsys SAML2 Sample for ASP.NET Core WebAPI without Identity
[Authorize(AuthenticationSchemes = Saml2Defaults.Scheme)]
public class AuthenticationController : Controller
{
public AuthenticationController()
{
}
[AllowAnonymous]
public async Task LoginAdfs()
{
string redirectUri = string.Concat("https://localhost:5000", "/verifyAdfs");
try
{
new ChallengeResult(
Saml2Defaults.Scheme,
new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(LoginCallback), new { redirectUri })
});
}catch(Exception e)
{
}
}
[AllowAnonymous]
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync(Saml2Defaults.Scheme);
//_log.Information("Authenticate result: {#authenticateResult}", authenticateResult);
// I get false here and no information on claims etc.
if (!authenticateResult.Succeeded)
{
return Unauthorized();
}
var claimsIdentity = new ClaimsIdentity("Email");
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
// _log.Information("Logged in user with following claims: {#Claims}", authenticateResult.Principal.Claims);
await HttpContext.SignInAsync("Email", new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
}
note: I've got a client that won't expose his MetaData in a URL, so I'll need to adapt it and set manually the metadata parameters
I'm stuck in this error, I does not even hit my method LoginAdfs.
The Saml2 handler cannot be used as an authencation scheme, it is a challenge scheme.
I guess that the LoginAdfs() method works fine, but that it's the LoginCallback that fails. The reason should be the call to HttpContext.AuthenticationAsync(Saml2Defaults.Scheme).
You should instead authenticate with the cookie scheme - because that's what keeps the session. Internally when the challenge is completed, the Saml2 handler will use the DefaultSignInScheme to preserve the result in a session (through a cookie, as that's the default sign in scheme).

Microsoft Graph: Current authenticated context is not valid for this request

I had an app that used MSAL and the v2.0 endpoint to sign in users and get token.
I recently changed it to ADAL and the normal AAD endpoint (also changing the app), and now when I try to use the GraphService I get the following error: Current authenticated context is not valid for this request
My user is admin
All permissions have been delegated
The token is successfully retrieved
Here is the code I use:
public static GraphServiceClient GetAuthenticatedClient()
{
GraphServiceClient graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await SampleAuthProvider.Instance.GetUserAccessTokenAsync();
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}));
return graphClient;
}
Calling the method, where the actual error happens:
try
{
// Initialize the GraphServiceClient.
GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient();
// Get events.
items = await eventsService.GetMyEvents(graphClient);
}
catch (ServiceException se)
{
}
Getting the token:
public async Task<string> GetTokenAsync()
{
ClientCredential cc = new ClientCredential(appId, appSecret);
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/tenant.onmicrosoft.com");
AuthenticationResult result = await authContext.AcquireTokenAsync("https://graph.microsoft.com", cc);
return result.AccessToken;
}
Can't find anything on this online so I am not sure how to continue.
Error:
This exception is caused by the token acquired using the client credentials flow. In this flow, there is no context for Me.
To fix this issue, you need to specify the whose event you want to get. Or you need to provide the delegate-token.
code for your reference:
//var envens=await graphClient.Me.Events.Request().GetAsync();
var envens = await graphClient.Users["xxx#xxx.onmicrosoft.com"].Events.Request().GetAsync();