OAuth 2.0 REST Web API and xamarin forms - c#

I make webservice api based on this tutorial https://www.c-sharpcorner.com/article/asp-net-mvc-oauth-2-0-rest-web-api-authorization-using-database-first-approach/ and I need to consume the service form xamarin forms. But I don't know how to authorize client.

Before you try authorizing in code, you should try talking to your API via an api client such as Postman.
You can see in step 11 of the article you reference that the writer is infact doing this.
He is performing the following steps:
Calling the token endpoint (no auth)
Adding the token to his subsequent requests
In order to call an API with authorization, you must first know the auth method (basic, OAuth etc). In this case you're saying it's OAuth:
Take a look at the guide, it shares this picture:
To do this in code you will need to add the following header to your http client. Lets assume you're using vanilla System.Net.Http.HttpClient you would need to implement a class that looks something like this:
public class APIClient
{
private HttpClient _client;
public APIClient()
{
_client = SetupClient();
}
private HttpClient SetupClient()
{
//setup your client here
var client = new HttpClient();
//string oauthToken = TokenService.GetUserToken();
string oauthToken = "eyJhbGciO......."; //Example token
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {oauthToken}");
//more setup here
return client;
}
public async Task<HttpResponseMessage> Get(string endpoint)
{
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
return await CallAsync(request);
}
private async Task<HttpResponseMessage> CallAsync(HttpRequestMessage request)
{
//do things before?
var result = await _client.SendAsync(request);
//handle result? null? not success code?
return result;
}
}
When you initialise your HttpClient you should add the following header:
Authorization : Bearer {yourtoken}
Now subsequent api requests will have authorization from your api client. How you set this bearer value is up to you. Some people store credentials in the xamarin main App class, and then retrieve the property. Other will persist the data into a plist and have the apiclient read this value (maybe credentials expire every 30 days).
Regardless there are a number of pitfalls that come with talking to api's from a xamarin app. You should always start by calling your api from outside of your app, from within an api client. This will teach you how to configure the requests correctly, without the overhead of worrying if your code/configuration is correct.

Please check my class if help you
`public class ServicesClient
{
private HttpClient httpClient;
private bool _IsConnection { get { return CheckInternet(); } }
public bool IsConnection { get { return _IsConnection; } }
public ServicesClient()
{
httpClient = new HttpClient(new HttpClientHandler());
//You can change the key as you need and add value
httpClient.DefaultRequestHeaders.Add("key", "000000");
}
//Get Method
public async Task<T> GetAsync<T>(string URL) where T : class
{
if (IsConnection)
{
var result = await httpClient.GetStringAsync(URL);
if (!string.IsNullOrEmpty(result))
return JsonConvert.DeserializeObject<T>(result);
else
return null;
}
return null;
}
//Post Method
public async Task<T> PostAsync<T>(string URL, object param) where T : class
{
if (IsConnection)
{
var json = JsonConvert.SerializeObject(param);
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
var result = await httpClient.PostAsync(URL, httpContent);
if (result.IsSuccessStatusCode)
return JsonConvert.DeserializeObject<T>(result.Content.ReadAsStringAsync().Result);
}
return null;
}
bool CheckInternet()
{
return Connectivity.NetworkAccess == NetworkAccess.Internet;
}
}
}`

Related

JWT doesn't get stored in ASP.NET Core with Blazor

I followed this tutorial: https://medium.com/#st.mas29/microsoft-blazor-web-api-with-jwt-authentication-part-1-f33a44abab9d
I downloaded the example: https://github.com/StuwiiDev/DotnetCoreJwtAuthentication/tree/Part2
I can see that the token is created but I don't understand how it is or should be saved on the client side as each time I access the SampleDataController, which has the Authorize tag, it returns a 401.
When calling and adding the token using Postman it works.
What am I missing for my user to be authenticated? Doesn't Microsoft.AspNetCore.Authentication.JwtBearer handle the client part (storing the token)?
What am I missing for my user to be authenticated? Doesn't Microsoft.AspNetCore.Authentication.JwtBearer handle the client part (storing the token)?
The JwtBearer runs on server side , it will only validate the authorization header of request, namely Authorization: Bearer your_access_token, and won't care about how you WebAssembly codes runs . So you need send the request with a jwt accessToken . Since the tutorial suggests you should use localStorage , let's store the accessToken with localStorage .
Because WebAssembly has no access to BOM yet, we need some javascript codes served as glue . To do that, add a helper.js under the JwtAuthentication.Client/wwwroot/js/ :
var wasmHelper = {};
wasmHelper.ACCESS_TOKEN_KEY ="__access_token__";
wasmHelper.saveAccessToken = function (tokenStr) {
localStorage.setItem(wasmHelper.ACCESS_TOKEN_KEY,tokenStr);
};
wasmHelper.getAccessToken = function () {
return localStorage.getItem(wasmHelper.ACCESS_TOKEN_KEY);
};
And reference the script in your JwtAuthentication.Client/wwwroot/index.html
<body>
<app>Loading...</app>
<script src="js/helper.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
</body>
Now, let's wrap the javascript codes into C# . Create a new file Client/Services/TokenService.cs:
public class TokenService
{
public Task SaveAccessToken(string accessToken) {
return JSRuntime.Current.InvokeAsync<object>("wasmHelper.saveAccessToken",accessToken);
}
public Task<string> GetAccessToken() {
return JSRuntime.Current.InvokeAsync<string>("wasmHelper.getAccessToken");
}
}
Register this service by :
// file: Startup.cs
services.AddSingleton<TokenService>(myTokenService);
And now we can inject the TokenService into Login.cshtml and use it to save token :
#using JwtAuthentication.Client.Services
// ...
#page "/login"
// ...
#inject TokenService tokenService
// ...
#functions {
public string Email { get; set; } = "";
public string Password { get; set; } = "";
public string Token { get; set; } = "";
/// <summary>
/// response from server
/// </summary>
private class TokenResponse{
public string Token;
}
private async Task SubmitForm()
{
var vm = new TokenViewModel
{
Email = Email,
Password = Password
};
var response = await Http.PostJsonAsync<TokenResponse>("http://localhost:57778/api/Token", vm);
await tokenService.SaveAccessToken(response.Token);
}
}
Let's say you want to send data within FetchData.cshtml
#functions {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
var token = await tokenService.GetAccessToken();
Http.DefaultRequestHeaders.Add("Authorization",String.Format("Bearer {0} ",token));
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
}
and the result will be :
Apologies in advance as this is somewhat responding to a previous answer, but I don't have the rep to comment on that.
If it helps anyone else who was similarly looking for a solution to using JWT in a Blazor app, I found #itminus answer incredibly useful, but it also pointed me to another course.
One problem I found was that calling FetchData.cshtml a second time would blow up when it tries to add the Authorization header a second time.
Instead of adding the default header there, I added it to the HttpClient singleton after a successful login (which I believe Blazor creates for you automatically). So changing SubmitForm in Login.cshtml from #itminus' answer.
protected async Task SubmitForm()
{
// Remove any existing Authorization headers
Http.DefaultRequestHeaders.Remove("Authorization");
TokenViewModel vm = new TokenViewModel()
{
Email = Email,
Password = Password
};
TokenResponse response = await Http.PostJsonAsync<TokenResponse>("api/Token/Login", vm);
// Now add the token to the Http singleton
Http.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0} ", response.Token));
}
Then I realised, than as I'm building a SPA, so I didn't need to persist the token across requests at all - it's just in attached to the HttpClient.
The following class handle the login process on the client, storing the JWT token in local storage. Note: It is the developer responsibility to store the JWT token, and passes it to the server. The client (Blazor, Angular, etc.) does not do that for him automatically.
public class SignInManager
{
// Receive 'http' instance from DI
private readonly HttpClient http;
public SignInManager(HttpClient http)
{
this.http = http;
}
[Inject]
protected LocalStorage localStorage;
public bool IsAuthenticated()
{
var token = localStorage.GetItem<string>("token");
return (token != null);
}
public string getToken()
{
return localStorage.GetItem<string>("token");
}
public void Clear()
{
localStorage.Clear();
}
// model.Email, model.Password, model.RememberMe, lockoutOnFailure: false
public async Task<bool> PasswordSignInAsync(LoginViewModel model)
{
SearchInProgress = true;
NotifyStateChanged();
var result = await http.PostJsonAsync<Object>("/api/Account", model);
if (result)// result.Succeeded
{
_logger.LogInformation("User logged in.");
// Save the JWT token in the LocalStorage
// https://github.com/BlazorExtensions/Storage
await localStorage.SetItem<Object>("token", result);
// Returns true to indicate the user has been logged in and the JWT token
// is saved on the user browser
return true;
}
}
}
// This is how you call your Web API, sending it the JWT token for // the current user
public async Task<IList<Profile>> GetProfiles()
{
SearchInProgress = true;
NotifyStateChanged();
var token = signInManager.getToken();
if (token == null) {
throw new ArgumentNullException(nameof(AppState)); //"No token";
}
this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// .set('Content-Type', 'application/json')
// this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
Profiles = await this.http.GetJsonAsync<Profile[]>("/api/Profiles");
SearchInProgress = false;
NotifyStateChanged();
}
// You also have to set the Startup class on the client as follows:
public void ConfigureServices(IServiceCollection services)
{
// Add Blazor.Extensions.Storage
// Both SessionStorage and LocalStorage are registered
// https://github.com/BlazorExtensions/Storage
**services.AddStorage();**
...
}
// Generally speaking this is what you've got to do on the client. // On the server, you've got to have a method, say in the Account controller, whose function is to generate the JWT token, you've to configure the JWT middleware, to annotate your controllers with the necessary attribute, as for instance:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
and so on...
Hope this helps...

C# Net Core 2.0 refactoring

Writing code for controllers could lead to repeat myself again and again.
How can reuse the code below and apply DRY principle on C# Net Core 2.0. MVC controllers?
See the below example.
The coding for getting a full list of departments using EF and web API is as follows..
[HttpGet]
public async Task<IActionResult> Department()
{
using (var client = await _apiHttpClient.GetHttpClientAsync())
{
var response = await client.GetAsync("api/Department");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var dptos = JsonConvert.DeserializeObject<Department[]>(content);
return View(dptos);
}
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
return RedirectToAction("AccessDenied", "Authorization");
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
}
Is indeed almost identical to get a single department..
[HttpGet]
public async Task<IActionResult> DeparmentEdit(string id)
{
ViewData["id"] = id;
using (var client = await _apiHttpClient.GetHttpClientAsync())
{
var response = await client.GetAsync($"api/Department/{id}");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var dpto = JsonConvert.DeserializeObject<Department>(content);
return View(dpto);
}
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
return RedirectToAction("AccessDenied", "Authorization");
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
}
The _apiHttpClient field holds a custom implementation of an HttpClient for tokens and refreshing tokens to access the web API.
I think that IS NOT relevant here to apply refactoring and DRY but anyway I will copy his implementation here below.
BR and thanks in advance for your reply.
public class ApiHttpClient : IApiHttpClient
{
private HttpClient _httpClient;
private HttpClient HttpClient => _httpClient ?? (_httpClient = new HttpClient());
private readonly IHttpContextAccessor _httpContextAccessor;
public ApiHttpClient(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<HttpClient> GetHttpClientAsync()
{
string accessToken;
var context = _httpContextAccessor.HttpContext;
var expiresAt = await context.GetTokenAsync(Constants.Tokens.ExpiresAt); // Get expires_at value
if (string.IsNullOrWhiteSpace(expiresAt) // Should we renew access & refresh tokens?
|| (DateTime.Parse(expiresAt).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow) // Make sure to use the exact UTC date formats for comparison
{
accessToken = await RefreshTokensAsync(_httpContextAccessor.HttpContext); // Get the current HttpContext to access the tokens
}
else
{
accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // Get access token
}
HttpClient.BaseAddress = new Uri(Constants.Urls.ApiHost);
if (!string.IsNullOrWhiteSpace(accessToken))
HttpClient.SetBearerToken(accessToken);
return HttpClient;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing)
{
if (_httpClient != null)
{
_httpClient.Dispose();
_httpClient = null;
}
}
}
public static async Task<string> RefreshTokensAsync(HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token client using the token end point. We will use this client to request new tokens later on
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the current refresh token
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken); // We request a new pair of access and refresh tokens using the current refresh token
if (tokenResponse.IsError)
return null; // Let's the unauthorized page bubbles up
// throw new Exception("Problem encountered while refreshing tokens", tokenResponse.Exception);
var expiresAt = (DateTime.UtcNow
+ TimeSpan.FromSeconds(tokenResponse.ExpiresIn)).ToString("O", CultureInfo.InvariantCulture); // New expires_at token ISO 860
var authenticateResult = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); // HttpContext.Authentication.GetAuthenticateInfoAsync() deprecated
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.AccessToken, tokenResponse.AccessToken); // New access_token
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, tokenResponse.RefreshToken); // New refresh_token
authenticateResult.Properties.UpdateTokenValue(Constants.Tokens.ExpiresAt, expiresAt); // New expires_at token ISO 8601 WHY _at TODO
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, authenticateResult.Principal, authenticateResult.Properties); // Signing in again with the new values, doing such a user relogin, ensuring that we change the cookies on client side. Doig so the user that has logged in has the refreshed tokens
return tokenResponse.AccessToken;
}
public static async Task RevokeTokensAsync(HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var revocationClient = new TokenRevocationClient(discoveryResponse.RevocationEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token revocation client using the token revocation endpoint. We will use this client to revoke tokens later on
var accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // Get the access token token to revoke
if (!string.IsNullOrWhiteSpace(accessToken))
{
var revokeAccessTokenTokenResponse = await revocationClient.RevokeAccessTokenAsync(accessToken);
if (revokeAccessTokenTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the access token.", revokeAccessTokenTokenResponse.Exception);
}
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the refresh token to revoke
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var revokeRefreshTokenResponse = await revocationClient.RevokeRefreshTokenAsync(refreshToken);
if (revokeRefreshTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the refresh token.", revokeRefreshTokenResponse.Exception);
}
}
}
I had refactored the code as follows having in mind the following workflow.
We will need: a) an API service class, b) a HttpContextAccessor and c) a HttpClient.
1) DI principle!. We register them in our dependency injection container at ConfigureServices
services
.AddTransient<IGameApiService, GameApiService>()
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
.AddSingleton(c => new HttpClient { BaseAddress = new Uri(Constants.Urls.ApiHost) });
2) The big job!. The new GameApiService will do the "heavy job" of calling our API methods. We will call the API using a "composed" request string. The API service will use our HttpClient, passing our request string and returning the response code and A STRING! (instead of using generics or other object) with the content. (I would need help on moving to generic since I fear that the registration on the dependency container will be "hard" to do with generics).
(the HttpContextAccessor is used for some token methods)
public class GameApiService : IGameApiService
{
private readonly HttpClient _httpClient;
private readonly HttpContext _httpContext;
public GameApiService(HttpClient httpClient, IHttpContextAccessor httpContextAccessor)
{
_httpClient = httpClient;
_httpContext = httpContextAccessor.HttpContext;
_httpClient.AddBearerToken(_httpContext); // Add current access token to the authorization header
}
public async Task<(HttpResponseMessage response, string content)> GetDepartments()
{
return await GetAsync(Constants.EndPoints.GameApi.Department); // "api/Department"
}
public async Task<(HttpResponseMessage response, string content)> GetDepartmenById(string id)
{
return await GetAsync($"{Constants.EndPoints.GameApi.Department}/{id}"); // "api/Department/id"
}
private async Task<(HttpResponseMessage response, string content)> GetAsync(string request)
{
string content = null;
var expiresAt = await _httpContext.GetTokenAsync(Constants.Tokens.ExpiresAt); // Get expires_at value
if (string.IsNullOrWhiteSpace(expiresAt) // Should we renew access & refresh tokens?
|| (DateTime.Parse(expiresAt).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow) // Make sure to use the exact UTC date formats for comparison
{
var accessToken = await _httpClient.RefreshTokensAsync(_httpContext); // Try to ge a new access token
if (!string.IsNullOrWhiteSpace(accessToken)) // If succeded set add the new access token to the authorization header
_httpClient.AddBearerToken(_httpContext);
}
var response = await _httpClient.GetAsync(request);
if (response.IsSuccessStatusCode)
{
content = await response.Content.ReadAsStringAsync();
}
else if (response.StatusCode != HttpStatusCode.Unauthorized && response.StatusCode != HttpStatusCode.Forbidden)
{
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
return (response, content);
}
}
public interface IGameApiService
{
Task<(HttpResponseMessage response, string content)> GetDepartments();
Task<(HttpResponseMessage response, string content)> GetDepartmenById(string id);
}
3) Great DRY! Our MVC controller will use this new API service as follows.. (we really don't have very much code there and THIS IS THE GOAL.. ;-) GREAT!!.
We still keep the responsibility of de-serialize the content string on the controller action on which the service API method was invoked. The code for the service API looks like...
[Route("[controller]/[action]")]
public class DepartmentController : Controller
{
private readonly IGameApiService _apiService;
public DepartmentController(IGameApiService apiService)
{
_apiService = apiService;
}
[HttpGet]
public async Task<IActionResult> Department()
{
ViewData["Name"] = User.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Name)?.Value;
var (response, content) = await _apiService.GetDepartments();
if (!response.IsSuccessStatusCode) return Forbid();
return View(JsonConvert.DeserializeObject<Department[]>(content));
}
[HttpGet]
public async Task<IActionResult> DepartmentEdit(string id)
{
ViewData["id"] = id;
var (response, content) = await _apiService.GetDepartmenById(id);
if (!response.IsSuccessStatusCode) return Forbid();
return View(JsonConvert.DeserializeObject<Department>(content));
}
}
4) Last trick!. To redirect to a custom page when we are not authorized or the permission has been denied we have issued if (!response.IsSuccessStatusCode) return Forbid(); yes Forbid(). But we still need to configure the default denied page on the cookie middleware. Thus on ConfigureServices we do it with services.AddAuthentication().AddCookie(AddCookie) methods, configuring the relevant options, mainly the AccessDeniedPath option as follows.
private static void AddCookie(CookieAuthenticationOptions options)
{
options.Cookie.Name = "mgame";
options.AccessDeniedPath = "/Authorization/AccessDenied"; // Redirect to custom access denied page when user get access is denied
options.Cookie.HttpOnly = true; // Prevent cookies from being accessed by malicius javascript code
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Cookie only will be sent over https
options.ExpireTimeSpan = TimeSpan.FromMinutes(Constants.CookieTokenExpireTimeSpan); // Cookie will expire automaticaly after being created and the client will redirect back to Identity Server
}
5) A word about the HTTP Client!. It will be instantiated using a factory on the dependency injection. A new instance is created per GameApiService instance.
The helper code to set the bearer token on the header and refresh the access token has been moved to a convenient extension method helper class as follows..
public static class HttpClientExtensions
{
public static async void AddBearerToken(this HttpClient client, HttpContext context)
{
var accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
if (!string.IsNullOrWhiteSpace(accessToken))
client.SetBearerToken(accessToken);
}
public static async Task<string> RefreshTokensAsync(this HttpClient client, HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token client using the token end point. We will use this client to request new tokens later on
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the current refresh token
var tokenResponse = await tokenClient.RequestRefreshTokenAsync(refreshToken); // We request a new pair of access and refresh tokens using the current refresh token
if (tokenResponse.IsError) // Let's the unauthorized page bubbles up instead doing throw new Exception("Problem encountered while refreshing tokens", tokenResponse.Exception)
return null;
var expiresAt = (DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn)).ToString("O", CultureInfo.InvariantCulture); // New expires_at token ISO 860
var authenticateResult = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); // HttpContext.Authentication.GetAuthenticateInfoAsync() deprecated
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.AccessToken, tokenResponse.AccessToken); // New access_token
authenticateResult.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, tokenResponse.RefreshToken); // New refresh_token
authenticateResult.Properties.UpdateTokenValue(Constants.Tokens.ExpiresAt, expiresAt); // New expires_at token ISO 8601
await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, authenticateResult.Principal, authenticateResult.Properties); // Signing in again with the new values, doing such a user relogin, ensuring that we change the cookies on client side. Doig so the user that has logged in has the refreshed tokens
return tokenResponse.AccessToken;
}
public static async Task RevokeTokensAsync(this HttpClient client, HttpContext context)
{
var discoveryResponse = await DiscoveryClient.GetAsync(Constants.Authority); // Retrive metadata information about our IDP
var revocationClient = new TokenRevocationClient(discoveryResponse.RevocationEndpoint, Constants.ClientMvc.Id, Constants.ClientMvc.Secret); // Get token revocation client using the token revocation endpoint. We will use this client to revoke tokens later on
var accessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); // Get the access token token to revoke
if (!string.IsNullOrWhiteSpace(accessToken))
{
var revokeAccessTokenTokenResponse = await revocationClient.RevokeAccessTokenAsync(accessToken);
if (revokeAccessTokenTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the access token.", revokeAccessTokenTokenResponse.Exception);
}
var refreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Get the refresh token to revoke
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var revokeRefreshTokenResponse = await revocationClient.RevokeRefreshTokenAsync(refreshToken);
if (revokeRefreshTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the refresh token.", revokeRefreshTokenResponse.Exception);
}
}
}
Now the code after refactoring it looks more pretty and clean.. ;-)
You could just split it up using generics. I haven't debugged this code (obviously), but I think it gets you where you need to go.
using System.Security.Authentication;
[HttpGet]
public async Task<IActionResult> Department() {
try {
var myObject = await GetSafeData<Department[]>("api/Department");
return view(myObj);
} catch(AuthenticationException ex) {
return RedirectToAction("AccessDenied", "Authorization");
}
}
internal T GetSafeData<T>(string url) {
using (var client = await _apiHttpClient.GetHttpClientAsync()) {
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode) {
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
Throw New AuthenticationException("");
throw new Exception($"A problem happened while calling the API: {response.ReasonPhrase}");
}
}
You can sorta see how you might pass response to that same method, so you could do your AccessDenied redirect within that method as well and reduce your repetitive code everywhere.
It's a generic method, so you can use it for ANY call to that api. That should be enough to get you started. Hope it helps!

Add multiple tokens into DefaultRequestHeaders.Authorization in HttpClient

The API I'm calling from my ASP.NET Web API app requires two tokens i.e. accessToken and userToken.
The following code is not working because it takes only the second token, not both. Looks like the second line is over-writing the first one.
How do I add multiple tokens to my request header?
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("APIAccessToken", "token1");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("UserToken", "token2");
UPDATE:
Here's the way I set this up and it's not working. Basically, my API calls seem to go nowhere. I get no errors. Just no response.
First, I have the HttpClientAccessor that looks like this:
public static class HttpClientAccessor
{
private static Lazy<HttpClient> client = new Lazy<HttpClient>(() => new HttpClient());
public static HttpClient HttpClient
{
get
{
client.Value.BaseAddress = new Uri("https://api.someurl.com");
client.Value.DefaultRequestHeaders.Accept.Clear();
client.Value.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client.Value;
}
}
}
I then have my ApiClient that will perform my API calls which looks like this:
public class MyApiClient
{
HttpClient _client;
public MyApiClient()
{
_client = HttpClientAccessor.HttpClient;
}
public async Task Get()
{
try
{
HttpResponseMessage response = await _client.GetAsync("/myendpoint"); // This is where it gets lost
var data = await response.Content.ReadAsStringAsync();
}
catch(Exception e)
{
var error = e.Message;
}
}
}
This is my controller action:
public class MyController : Controller
{
private readonly MyApiClient _client;
public MyController()
{
_client = new MyApiClient();
}
public IActionResult SomeAction()
{
_client.Get().Wait();
}
}
You are confusing the standard authorization header with custom headers
According to the linked documentation
Request Header
Add the generated tokens to the request headers "APIAccessToken" and "UserToken"
Example Request
APIAccessToken: zjhVgRIvcZItU8sCNjLn+0V56bJR8UOKOTDYeLTa43eQX9eynX90QntWtINDjLaRjAyOPgrWdrGK12xPaOdDZQ==
UserToken: 5sb8Wf94B0g3n4RGOqkBdPfX+wr2pmBTegIK73S3h7uL8EzU6cjsnJ0+B6vt5iqn0q+jkZgN+gMRU4Y5+2AaXw==
To get headers like above, add them to the client like below
_client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
_client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
Based on shown update, the client is adding the headers every time the client is called. This should be in the value factory of the lazy client.
public static class HttpClientAccessor {
public static Func<HttpClient> ValueFactory = () => {
var client = new HttpClient();
client.BaseAddress = new Uri("https://someApiUrl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient {
get {
return client.Value;
}
}
}
The controller action also needs to be refactored to avoid deadlocks because of the mixing of async and blocking calls like .Wait() or .Result.
public class MyController : Controller {
private readonly MyApiClient _client;
public MyController() {
_client = new MyApiClient();
}
public async Task<IActionResult> SomeAction() {
await _client.Get();
//... code removed for brevity
}
}

Setting cookies to httpClient of Asp.Net Core TestServer

I,m testing ASP.NET Core app with TestServer, and there are controllers that require cookie auth. I've created test server instance like this:
_testServer = new TestServer(new WebHostBuilder()
.UseEnvironment(CustomEnvironments.Test)
.UseContentRoot(currentDirectory)
.UseStartup<Web.Startup>()
.UseUrls("http://localhost/"));
ApiClient = _testServer.CreateClient();
and now I have to add auth cookie, but it is ignored by server. If the client could be created directly I could pass HttpClientHandler to constractor and set UseCookies to false, and it works, but I can't access the handler when I get client from test server. Is there a way to add auth cookies to test client?
I've found the solution. TestServer has method CreateRequest(string path), it returns RequestBuilder, which allows to insert cookies to header
Using #AlexK's answer for inspiration, combined with information from a blog post (as an aside, this post goes into a lot of other useful details when dealing with other issues when sending requests to the test server), here is one way of getting cookies to work with the TestServer using CreateRequest(string path) based on what I used for my own project.
public class TestWebEnvironment : IDisposable
{
private TestServer Server { get; }
private CookieContainer CookieContainer { get; }
public TestWebEnvironment()
{
var builder = new WebHostBuilder()
.UseEnvironment("Test")
.UseStartup<TestWebStartup>();
Server = new TestServer(builder);
CookieContainer = new CookieContainer();
}
private RequestBuilder BuildRequest(string url)
{
var uri = new Uri(Server.BaseAddress, url);
var builder = Server.CreateRequest(url);
var cookieHeader = CookieContainer.GetCookieHeader(uri);
if (!string.IsNullOrWhiteSpace(cookieHeader))
{
builder.AddHeader(HeaderNames.Cookie, cookieHeader);
}
return builder;
}
private void UpdateCookies(string url, HttpResponseMessage response)
{
if (response.Headers.Contains(HeaderNames.SetCookie))
{
var uri = new Uri(Server.BaseAddress, url);
var cookies = response.Headers.GetValues(HeaderNames.SetCookie);
foreach (var cookie in cookies)
{
CookieContainer.SetCookies(uri, cookie);
}
}
}
public async Task<string> GetAsync(string url)
{
using (var response = await BuildRequest(url).GetAsync())
{
UpdateCookies(url, response);
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> PostAsync(string url, HttpContent content)
{
var builder = BuildRequest(url);
builder.And(request => request.Content = content);
using (var response = await builder.PostAsync())
{
UpdateCookies(url, response);
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
Server.Dispose();
}
}

Call Web API from MVC Controller using the same HttpClient

I have an MVC5 project, from the MVC controller I need to call the Web API method. The Web API uses token based authentication, so I have to pass the token for each call. I am using the code below to pass the token in the HTTP header:
HttpClient httpClient = new HttpClient();
string baseUrl = "http://localhost:60477/";
dynamic token = Session["token"];
if (token.AccessToken != null)
{
httpClient.DefaultRequestHeaders.Add("Authorization", String.Format("Bearer {0}", token.AccessToken));
}
There are multiple action methods in my controller, and I want to use a single HttpClient and headers, added in one place, instead of adding a header in each and every action method.
Where can I place the HttpClient headers registration code in the MVC application, so it can be common to all controllers? That means I don't want to repeat code, like adding the token in each and every action method. How can I do that?
Public ActionResult Postuser(UserModel user)
{
// post code
}
Public ActionResult getuser(UserModel user)
{
HttpResponseMessage response = httpClient.GetAsync(baseUrl + "api/Admin/GetStates").Result;
if (response.IsSuccessStatusCode)
{
string stateInfo = response.Content.ReadAsStringAsync().Result;
}
}
Public ActionResult PostRoles(RoleModel role)
{
// post roles code
}
You can try creating a small helper class for creating your httpclient object. Something like
public class HttpClientHelper
{
public static HttpClient GetHttpClient()
{
var MyHttpClient = new HttpClient();
dynamic _token = HttpContext.Current.Session["token"];
if (_token == null) throw new ArgumentNullException(nameof(_token));
MyHttpClient.DefaultRequestHeaders.Add("Authorization", String.Format("Bearer {0}", _token.AccessToken));
return MyHttpClient;
}
}
and then call it in your controllers as
public ActionResult getuser(UserModel user)
{
var httpClient = HttpClientHelper.GetHttpClient();
HttpResponseMessage response = httpClient.GetAsync(baseUrl + "api/Admin/GetStates").Result;
if (response.IsSuccessStatusCode)
{
string stateInfo = response.Content.ReadAsStringAsync().Result;
}
}
It is better to adhere to the Single Responsibility Principle and extract the interaction with another service in a it's own class, e.g.
public class ServiceClient : IServiceClient
{
private HttpClient m_Client;
public ServiceClient
{
m_Client = new HttpClient();
// Initialize the client as you need here
}
public void CallSomeMethod()
{
// Call method on the client
}
}
Then you inject the IServiceClient in your controller and just call it's methods. If you do not use injection (which I advise you do) you can just create a new instance in the controller's constructor.
You can try using an action filter in your controller. Try adding an override that looks something like this-
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// some condition code to target a specific method in the controller
// Example
if (filterContext.ActionDescriptor.ActionName == "getuser") // <-- your method
{
// put your token based authentication code here
}
base.OnActionExecuting(filterContext);
}
The OnActionExecuting method is at the controller scope so you can have different logic for different controllers.
There's also an OnActionExecuted method override if you want to run code after your action method.
------edit--------------
As far as where to place your HttpClient code snippet, you can try this-
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpClient httpClient = new HttpClient();
string baseUrl = "http://localhost:60477/";
dynamic token = Session["token"];
if (token.AccessToken != null)
{
httpClient.DefaultRequestHeaders.Add(
"Authorization",
string.Format("Bearer {0}", token.AccessToken)
);
httpClient.BaseAddress = new Uri(baseUrl);
}
if(filterContext.ActionParameters.ContainsKey("httpClient"))
{
filterContext.ActionParameters["httpClient"] = httpClient;
}
else
{
// error
}
base.OnActionExecuting(filterContext);
}
So the HttpClient object along with the assignment of your baseUrl is established in OnActionExecuting. This code will run before any method returning a ActionResult in the controller you are refactoring. If you want to target some and not all methods, see the first example of OnActionExecuting above.
public ActionResult getuser(UserModel user, HttpClient httpClient)
{
HttpResponseMessage response = httpClient.GetAsync("api/Admin/GetStates").Result;
if(response.IsSuccessStatusCode)
{
string stateInfo = response.Content.ReadAsStringAsync().Result;
}
// the rest of your code for getuser..
return View();
}
Now your getuser method has an extra parameter ( HttpClient httpClient ).
why don't you move the code in Global asax or create custom Atribute?
here is one good link:
http://www.diaryofaninja.com/blog/2011/07/24/writing-your-own-custom-aspnet-mvc-authorize-attributes

Categories

Resources