Handling JWT expiration in .NET MVC-application - c#

I have an ASP.NET MVC-application which is storing a JWT-token and a refresh token from my Web API in Session. My question is what to do when the JWT-token expires and it is time to refresh it. As I see it my two options are:
Try to make a request to the Web API using the JWT-token and if it returns 401 Unauthorized, try refreshing the JWT-token.
Using a timer to automatically refresh the JWT-token before it expires.
What are advantages of using either of these two methods, and how can I programatically implement them in an easy way? For example, do I have to use a try and catch for every call to the API if i use option 1?

I decided to go with option 2 in order to minimize the number of calls to the API. I then created a base controller class with a HttpClient factory method, which also checks if the JWT is about to expire:
public HttpClient GetHttpClient(string baseAdress)
{
var client = new HttpClient();
client.BaseAddress = new Uri(baseAdress);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string token;
if (Session["access_token"] != null)
{
var jwthandler = new JwtSecurityTokenHandler();
var jwttoken = jwthandler.ReadToken(Session["access_token"] as string);
var expDate = jwttoken.ValidTo;
if (expDate < DateTime.UtcNow.AddMinutes(1))
token = GetAccessToken().Result;
else
token = Session["access_token"] as string;
}
else
{
token = GetAccessToken().Result;
}
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
Session["access_token"] = token;
return client;
}

Related

How to call a web api that has Oauth 2.0

Hi so we have an external web api we want to call to get data out. It is using oauth 2.0. Can somebody please explain how we would go about doing this in .NET either vb.net or c#. I have in the past created api, however this one seems very complicated. Firstly you have to be signed into their oauth web page they have which generates some cookies, using these cookies by syncing them up in postman we can see the data, however we need this to be within our .net app. Can somebody please help how we go about this. Some code would be useful.
Thanks
This is how usually OAuth 2 authentication works.
You basically log in with username and password (optional second factor) and then you receive a token, the so called Json Web Token or JWT (it holds encrypted information about your user, your access roles or groups you are member of as well as some timestamp which is the expiration time of the token).
In every subsequent request you make to the server, you pass this token in the request header (or in your case as cookie).
Example code:
Login request:
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUrl, "token"));
string body = JsonConvert.SerializeObject(new
{
Username = _userName,
Password = _password,
secondFactor = secondFactor
});
httpRequest.Content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.SendAsync(httpRequest);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
TokenResult r = JsonConvert.DeserializeObject<TokenResult>(responseContent);
if (!string.IsNullOrWhiteSpace(r.token))
{
_token = r.token;
_tokenValidity = r.expirationDate;
_refreshToken = r.refreshToken;
_refreshTokenValidity = r.refreshTokenExpirationDate;
return _token;
}
else
{
throw new Exception($"Failed to get token from server.\r\n{responseContent}");
}
}
Now you use the _token in subsequent requests in the request header:
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
using HttpResponseMessage response = await client.GetAsync(new Uri(_baseUrl, relativePath));
if (response.IsSuccessStatusCode)
{
using var stream = await response.Content.ReadAsStreamAsync();
stream.Position = 0;
using var reader = new StreamReader(stream);
reader.ReadToEnd();
}
Please note, that usually the token has a certain lifetime after which it is basically useless. Some APIs offer a refresh token with which a new token can be requested without the user having to log in again with username and password, but that's beyond the scope of this question.
You said you have to use the token as cookie? Well there are APIs which work like this but personally I've never seen one like this, which is why I can't you help very much, but it shouldn't be much more than putting the token you got into a cookie with a certain name.
Hope this helps.
Not sure what you are asking. I have a controller code where I use web api call to authenticate user. You can use your own model to pass the data. If your web api expects token for request, then you might have to get the token first to give a call to any method. Hope this helps.
OktaUserDetailsModel Model = new OktaUserDetailsModel();
Model.username = model.UserName;
Model.password = model.Password;
using (var httpClient = new HttpClient())
{
HttpContent inputContent = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(Model), System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = httpClient.PostAsync(ConfigurationManager.AppSettings["OktaAPIuri"], inputContent).Result;
if (response.IsSuccessStatusCode)
{
string strResponse = (new JavaScriptSerializer()).Deserialize<string>(response.Content.ReadAsStringAsync().Result);
if (strResponse.ToUpper() == "TRUE")
return OktaSingleSignOnLogin(astrReturnUrl, model.UserName);
else
return ErrorPage();
}
else
{
return ErrorPage();
}
}

OpenID Connect Authentication Successful. Now what?

I'm writing a windows service in C# that needs to authenticate with an API and make some calls. I'm able to authenticate successfully with this API I'm talking to, but I can't seem to figure out how to use the response. The response looks like this:
{"access_token":"Es-Zjs_LI0tcXyLe3aEfgKPNLHN7CwyUhTss-cTld1A","expires_in":1800,"token_type":"Bearer","scope":"example","auth_state":1,"company":"examplecompany"}
I can get the access token out of that string if I want, but no matter how I pass it to a request, I get a 401 error. This is what my current iteration looks like:
string results = "";
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token);
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://example.ca/endpoint"),
//Headers =
//{
// { "authorization", "Bearer"},
//},
};
try
{
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
results = body;
}
}
catch (Exception ex)
{
results = "ERROR: " + ex.Message;
}
return results;
Where "token" is the string "Es-Zjs_LI0tcXyLe3aEfgKPNLHN7CwyUhTss-cTld1A" in this example. I had previously tried stitching the access_token value as a string to the "Bearer" string in the commented out section in the middle there. What am I doing wrong? Do I need to make a JwtSecurityToken out of the response?
AuthenticationResult authResult = await daemonClient.AcquireTokenForClient(new[] { MSGraphScope })
.ExecuteAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
I've used the authResult.AccessToken. Not sure if it works in your scenario. The return type in my case was Microsoft.Identity.Client.AuthenticationResult type when I retrieved the token for a Graph API that I was using.
Be aware that the token you have received ("Es-Zjs_LI0tcXyLe3aEfgKPNLHN7CwyUhTss-cTld1A") is a reference token and not a JWT-token. Make sure your API accepts that type of token.
To use the token effectively in production then I would consider using the various helper methods found in the IdentityModel library and especially the Worker application helpers.
While I understand it's largely situational depending on what API you're trying to connect to, for me the solution was to use this method to pass in the authentication token:
request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + token);

CookieAuthentication in .NET Core expires while using MVC App which uses that external API

I have an API in .NET Core which source code is available here. Now I'm building an MVC application which uses that API. The problem is about CookieAuthentication system in the API.
I have this code in my Configure method in Startup.cs:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "CookieAuthentication",
LoginPath = new PathString("/users/login"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
In my login method (HttpPost) I set my cookie like this:
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, request.Email)
};
var userIdentity = new ClaimsIdentity(claims, "login");
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
await HttpContext.Authentication.SignInAsync("CookieAuthentication", principal);
return StatusCode(302);
Then in every method which requires authorization I check if user is logged in like this:
if (email != await GetLoggedUserEmail())
{
throw new ServiceException(ErrorCodes.UserNotFound, "You can only edit information on your account.");
}
// do some logic
...
public async Task<string> GetLoggedUserEmail()
{
if (HttpContext.User.Identity.Name == null)
{
throw new ServiceException(ErrorCodes.UserNotFound, "There is no logged in user.");
}
return await Task.FromResult(HttpContext.User.Identity.Name);
}
The problem is that when I log in by using for example Fiddler for requests and then try to delete some information by the HttpDelete request the cookie authentication works correct (I can only edit or delete informations about myself. I can't edit or delete not mine information because it will check that I'm the wrong logged user to do that. That's great.).
But when I try to do it in my MVC application strange thing happens.
When I login in the API like this:
using (var client = new HttpClient())
{
var content = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://www.pets.pawelkowalewicz.pl/users/login", content);
}
Then my response.StatusCode is 302 (which is correct according to the API). And then when I try to delete some information on the other page using this request:
using (var client = new HttpClient())
{
var response = await client.DeleteAsync("http://www.pets.pawelkowalewicz.pl/users/kowalewicz.pawel#gmail.com/Animals/aaa");
}
Then response.StatusCode is 400! Seems like the cookie that I'm logged in expired or something...
But when I try to execute this request right after I login like this:
var response = await client.PostAsync("http://www.pets.pawelkowalewicz.pl/users/login", content);
var response2 = await client.DeleteAsync("http://www.pets.pawelkowalewicz.pl/users/kowalewicz.pawel#gmail.com/Animals/aaa");
Response2.StatusCode is correct and the information is deleted. I really don't know why it is happening.
Seems like this is some problem that occurres while I'm redirecting throught few pages before I try to execute delete request. When I do it right after login request in the same method it works fine.
Please help me with this problem.
The problem is that HttpClientHandler instance is storing cookies inside CookieContainer and you are not sharing the container.
You may want to create shared CookieContainer instance.
private static readonly cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
{
using (var client = new HttpClient(handler))
{
// Do your work here
}
}
Or after successful login extract cookies from handler.
using (var client = new HttpClient())
{
var content = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://www.pets.pawelkowalewicz.pl/users/login", content);
CookieCollection cookies = client.Handler.CookieContainer.GetCookies("http://www.pets.pawelkowalewicz.pl/");
// Store cookies collection somewhere it suits you
}
Then add to new instance of CookieContainer each time you instantiate new HttpClient.
CookieContainer cookieContainer = new CookieContainer();
CookieCollection cookies = LoadCookiesFromSomewhereYouStoredIt();
cookieContainer.Add("http://www.pets.pawelkowalewicz.pl/", cookies);
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
{
using (var client = new HttpClient(handler))
{
// Do your work here
}
}

Web API Call from a C# console application with credentials

I am trying to write a Web API site with a Get method that is Authorized. The site is a default template site, using Individual Accounts. So it stores the username and password in a database. I am attempting to call this Web API site and pass along a username and password in a console application via HttpClient. I have tried several ways of going about this. I think* i have CORS enabled on my API site. I keep getting Unauthorized results. Here is the HttpClient code I am running, I feel like it is completely valid, and I think something needs to be configured to handle this username and password on the API side, but I am completely unsure how to go about it if that is the case.
using (var client = new HttpClient())
{
var byteArray = Encoding.ASCII.GetBytes("sampleUser:Test123!");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
client.BaseAddress = new Uri("http://localhost:15198/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
HttpResponseMessage response = await client.GetAsync("api/Query");
if (response.IsSuccessStatusCode)
{
thing = response.Content.ToString();
}
}
catch (HttpRequestException e)
{
var test = e.Message;
}
}
you would need to impersonate and pass the credentials assuming your running windows authentication on your server.
using (new Impersonator(UserName, Domain, Pwd))
{
...http request
}
See thread

ThinkTecture Identity Model Get User based on Token

I am using the sweet Indentity Model library from Thinktecture. I looked high and low and am probably about to ask a dumb question. Basically, we have a Web Forms application which I am adding ASP.net Web api capabilities to it. I installed the Identity code samples and gleened what I think I needed to get it working for session token I am able to get a token and send back on future calls but in my API controller the user object does not seem to be set. I had assumed the framework would interrogate the token if provided and automatically set the Principal based on the token data and mark principal as authenticated? Is this an invalid assumption and is this something that I must set myself?
Basically I am using the token for Authorization and Authentication but need to pull off UserID from the token/session for additional business rules.
Again if dumb question feel free to haze me. I am testing using Unit Test if that has any impact on any recommendations.
Code from Unit Test:
1) Get Token, successfully validates user using our business logic and returns token successfully:
private string GetToken(string username, string password)
{
Uri _baseAddress = new Uri(Thinktecture.Samples.Constants.WebHostBaseAddress);
var client = new HttpClient { BaseAddress = _baseAddress };
client.SetBasicAuthentication(username, password);
var response = client.GetAsync("token").Result;
response.EnsureSuccessStatusCode();
var tokenResponse = response.Content.ReadAsStringAsync().Result;
var json = JObject.Parse(tokenResponse);
var token = json["access_token"].ToString();
var expiresIn = int.Parse(json["expires_in"].ToString());
var expiration = DateTime.UtcNow.AddSeconds(expiresIn);
return token;
}
2) HTTP HELPER METHOD:
private async Task<HttpResponseMessage> Post<T>(string path, T data, string Token)
{
//var handler = new HttpClientHandler {};
//using (var client = new HttpClient(handler))
using (var client = new HttpClient())
{
if (!String.IsNullOrEmpty(Token))
{
client.SetToken("Session", Token);
//client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Session", Token);
}
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.BaseAddress = new Uri("http://localhost/");
var response = await client.PostAsJsonAsync(path, data);
response.EnsureSuccessStatusCode(); // Throw on error code.
return response;
}
}
3) Calling Code:
string Token = GetToken("UserName", "Password");
removed proprietry code here....
Task<HttpResponseMessage> result = Post<GetCustomerDetailsRequest>("api/Account/GetCustomerProfile", GetCustomerDetailsRequest, Token);

Categories

Resources