How to read object value from returned API response? - c#

I have a web API that will validate a login from a client (console) and I want to retrieve the object (content) from the response returned from the console. Here is the snippet of the API code:
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] Login login)
{
if (ModelState.IsValid)
{
var user = await this.userManager.FindByNameAsync(login.Username);
if (user != null)
{
var passwordCheck = await this.signInManager.CheckPasswordSignInAsync(user, login.Password, false);
if (passwordCheck.Succeeded)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.config["Tokens:Key"]));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
this.config["Tokens:Issuer"],
this.config["Tokens:Audience"],
claims,
expires: DateTime.UtcNow.AddHours(3),
signingCredentials: credentials
);
return Ok(new
{
Token = new JwtSecurityTokenHandler().WriteToken(token),
Expiration = token.ValidTo
});
}
else
{
return Unauthorized("Wrong password or email!");
}
}
else
{
return Unauthorized("Must not empty");
}
}
return BadRequest();
}
If I tried to test the call from Postman, it will get what I expected:
However, I don't know how to get the same result from the console app. Here is the code:
static async Task<JwtToken> Login()
{
Login login = new();
JwtToken token = null;
Console.Write("Username: ");
login.Username = Console.ReadLine();
Console.Write("Password: ");
login.Password = Console.ReadLine();
HttpResponseMessage response = await client.PostAsJsonAsync("account/login", login);
if (response.IsSuccessStatusCode)
{
token = await response.Content.ReadAsAsync<JwtToken>();
Console.WriteLine("Token: {0}\nValid to: {1}",token.Token,token.Expiration);
return token;
}
else
{
Console.WriteLine(response.StatusCode.ToString());
return token;
}
}
And instead of the object, I will just get "BadRequest" or "Unauthorized". Thanks in advance if you can help me.

As the response's content is a string, you can achieve by reading the response.Content as string:
var errorMessage = await response.Content.ReadAsAsync<string>();
Place it in else statement.
else
{
var errorMessage = await response.Content.ReadAsAsync<string>();
Console.WriteLine(response.StatusCode.ToString());
return token;
}

Related

Teams Outgoing WebHook HMAC problem not matching

I created an outgoing Teams webhook.
The callback URL points to a controller on my API, and I would like to use the HMAC provided by the webhook in the request header.
However, when I compute the HMAC with the secret key, I don't obtain the same key as the one in the header.
I tried this code :
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
if (!this.Request.Headers.TryGetValue("Authorization", out var headerValue))
{
return AuthenticateResult.Fail("Authorization header not found.");
}
var sentKey = headerValue.ToString().Replace("HMAC ", null);
string requestBody = null;
using (var reader = new StreamReader(this.Request.Body, Encoding.UTF8))
{
requestBody = await reader.ReadToEndAsync();
}
if (string.IsNullOrWhiteSpace(requestBody))
{
return AuthenticateResult.Fail("No content to authenticate.");
}
var secretKeyBytes = Encoding.UTF8.GetBytes(this.Options.SecretKey);
using (var hmac = new HMACSHA256(secretKeyBytes))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(requestBody));
var expectedSignature = WebEncoders.Base64UrlEncode(hash);
if (!string.Equals(sentKey, expectedSignature, StringComparison.Ordinal))
{
return AuthenticateResult.Fail("Invalid HMAC signature.");
}
}
var claimsIdentity = new ClaimsIdentity();
var ticket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), this.Scheme.Name);
return AuthenticateResult.Success(ticket);
}
catch (Exception ex)
{
return AuthenticateResult.Fail($"{ex.HResult}, {ex.Message}");
}
}

Best place to implement Refresh token functionality in .Net Core?

We want to generate a new access token using a refresh token in the identity server. We want to implement a similar scenario as of SPA application(Silent renew token in react, angular). For now, we have implemented it in the Index section but the problem here is whenever I load the page only by new access token will be generated with the help of a refresh token.
public async Task<IActionResult> IndexAsync()
{
string accessToken = string.Empty;
var currentContext = _httpContextAccessor.HttpContext;
var expires_at = await currentContext.GetTokenAsync("expires_at");
if (string.IsNullOrWhiteSpace(expires_at)
|| ((DateTime.Parse(expires_at).AddSeconds(-60)).ToUniversalTime()
< DateTime.UtcNow))
{
accessToken = await RenewTokens();
}
else
{
accessToken = await HttpContext.GetTokenAsync("access_token");
}
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = await client.GetStringAsync("https://xxxxxxx");
ViewData["Token"] = content;
ViewData["AccessToken"] = accessToken;
return View();
}
public async Task<String> RenewTokens()
{
var currentContext = _httpContextAccessor.HttpContext;
var refreshToken = await HttpContext.GetTokenAsync("refresh_token");
var client = new HttpClient();
var values = new Dictionary<string, string>
{
{ "client_id", "xxx" },
{ "client_secret", "xxxx" },
{ "grant_type", "refresh_token" },
{ "scope", "xxxx" },
{"refresh_token", refreshToken }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https:///xxxxxx/token", content);
var jsonContent = await response.Content.ReadAsStringAsync();
Token tok = JsonConvert.DeserializeObject<Token>(jsonContent);
string access_token = tok.AccessToken;
var ExpiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tok.ExpiresIn);
var authenticationInfo = await currentContext.AuthenticateAsync("Cookies");
authenticationInfo.Properties.UpdateTokenValue("expires_at", ExpiresAt.ToString("o", CultureInfo.InvariantCulture));
authenticationInfo.Properties.UpdateTokenValue("access_token", tok.AccessToken);
authenticationInfo.Properties.UpdateTokenValue("refresh_token", tok.RefreshToken);
await currentContext.SignInAsync("Cookies", authenticationInfo.Principal, authenticationInfo.Properties);
return access_token;
}
Avoid basing renewal solely on expires_at since it is not resilient due to clock differences and race conditions. In some setups you may get a 401 for other reasons, such as token signing certificate renewal.
The responsibilities should be like this:
Client tries an API request with an access token
If client receives a 401 it attempts to silently refresh the access token and retry the API request with the new token
If your 'SPA' sends all requests for data via a Web back end you can maybe do this in C# code
Here is some sample code for resilient clients:
Web
Mobile

HttpClient not returning NotFound content from WebAPI

Web API Code:
// POST api/<MAUserController>
[HttpPost("AuthenticateUser")]
public async Task<ActionResult<MAUser>> PostAsync([FromHeader] string Email, [FromHeader] string Password)
{
string connString = configuration.GetConnectionString("DefaultConnection");
MAUserVM user = new MAUserVM();
user = await user.AuthenticateUserAsync(Email, Password, connString);
if (user.AuthenticationCode == 0)
{
return Ok(user._MAUser);
}
else if (user.AuthenticationCode == 100)
{
return NotFound("Email not found");
}
else if (user.AuthenticationCode == 200)
{
return NotFound("Incorrect password");
}
else
{
return NotFound();
}
}
Client Code:
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenString);
httpClient.DefaultRequestHeaders.Add("Email", Email);
httpClient.DefaultRequestHeaders.Add("Password", Password);
using (var response = await httpClient.PostAsync(API_URL, stringContent))
{
if (response.IsSuccessStatusCode)
{
string apiResponse = await response.Content.ReadAsStringAsync();
user = JsonConvert.DeserializeObject<MAUser>(apiResponse);
}
else
{
var str = response.StatusCode;
}
}
}
return user;
I only get Not Found in var str, but never the associated content - 'Email not found" or "Incorrect Password"
You are not reading the content of the response, only the status code.
using (var httpClient = new HttpClient()) {
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenString);
httpClient.DefaultRequestHeaders.Add("Email", Email);
httpClient.DefaultRequestHeaders.Add("Password", Password);
using (var response = await httpClient.PostAsync(API_URL, stringContent)) {
if (response.IsSuccessStatusCode) {
string apiResponse = await response.Content.ReadAsStringAsync();
user = JsonConvert.DeserializeObject<MAUser>(apiResponse);
} else {
var status = response.StatusCode;
if (status == HttpStatusCode.NotFound
&& response.Content.Headers.ContentLength.GetValueOrDefault() > 0) {
string content = await response.Content.ReadAsStringAsync();
}
}
}
}
return user;

GetAccessTokenAsync silently fails, unable to catch issue in debugger

I'm trying to get a GraphServiceClient so I can integrate with Teams.
When I try to get a token so I can autenticate with AAD, the line
accessToken = await azureServiceTokenProvide.GetAccessTokenAsync("https://graph.microsoft.com/");
Fails silently and the application exits, I'm not able to catch any exception or see the issue.
private async Task<GraphServiceClient> GetGraphApiClient()
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = "";
try
{
accessToken = await azureServiceTokenProvider
.GetAccessTokenAsync("https://graph.microsoft.com/");
} catch(AggregateException e)
{
Console.WriteLine("");
} catch (Exception ex)
{
Console.WriteLine("");
}
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.CompletedTask;
}));
return graphServiceClient;
}
Silly me, forgot to await the method:
GraphServiceClient graphClient = await GetGraphApiClient();

Quickbooks Online sandbox returns Waiting for Activation, i have realmId, accesstoken aswell

My Code is as follow:-
i have no idea why i am receiving this message, please help. Right now
i am using sandbox account to test this. I have generated the data i.e. sample data from API explorer and i am passing it as a parameter as Json.
public bool GeneratePayment(string JsonData)
{
var principal = User as ClaimsPrincipal;
Session["realmId"] = "XXXXXX";
if (Session["realmId"] != null)
{
string realmId = Session["realmId"].ToString();
string qboBaseUrl = ConfigurationManager.AppSettings["QBOBaseUrl"];
//add qbobase url and query
string uri = string.Format("{0}/v3/company/{1}/invoice", qboBaseUrl, realmId);
try
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json;charset=UTF-8");
client.DefaultRequestHeaders.Add("ContentType", "application/json;charset=UTF-8");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + "XXXX");
//Here i am getting waiting for activation
var result = client.PostAsync(uri, new StringContent(JsonData, System.Text.Encoding.UTF8, "application/json"));
return true;
}
catch (Exception ex)
{
return false;
}
}
else
return false;
}
Has to do with the Task associated with PostAsync.
The GeneratePayment method needs to be made async and client.PostAsync needs to be awaited as well
public async Task<bool> GeneratePayment(string JsonData) {
var principal = User as ClaimsPrincipal;
Session["realmId"] = "XXXXXX";
if (Session["realmId"] != null) {
string realmId = Session["realmId"].ToString();
string qboBaseUrl = ConfigurationManager.AppSettings["QBOBaseUrl"];
//add qbobase url and query
string uri = string.Format("{0}/v3/company/{1}/invoice", qboBaseUrl, realmId);
try {
var client = http.Value; //singleton http client
var result = await client.PostAsync(uri, new StringContent(JsonData, System.Text.Encoding.UTF8, "application/json"));
return true;
} catch (Exception ex) {
return false;
}
}
else
return false;
}
//Singleton lazy loaded HttpClieny
static Lazy<HttpClient> http = new Lazy<HttpClient>(() => {
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json;charset=UTF-8");
client.DefaultRequestHeaders.Add("ContentType", "application/json;charset=UTF-8");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + "XXXX");
return client;
});

Categories