Web API - Get information encrypted inside token, ticket ExpiresUtc and IssuedUtc - asp.net-web-api

I am using Web API as my back-end and implemented the token security using the built in mechanism. In the template code, when issuing the access token, I can get the issued and expired dates of the token:
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
var issued = context.Properties.IssuedUtc;
var expired = context.Properties.ExpiresUtc;
.
.
.
}
Now when a request is made to a method that requires authorization I want to do something similar:
[Authorize]
public async Task<string> GetTokenInfo()
{
//var issued = GetCurrentTicket().Properties.ExpiresUtc;
//var issued = GetCurrentTicket().Properties.IssuedUtc;
.
.
.
}
So how can I get the information encrypted inside the token, more specifically the ExpireUtc and IssuedUtc ?

You can easily retrieve the AuthenticationProperties dictionary using IAuthenticationManager.AuthenticateAsync, which returns a AuthenticateResult object: https://msdn.microsoft.com/en-us/library/dn270674(v=vs.113).aspx
From a Web API controller, you'll need the GetOwinContext extension to get the OWIN context from the request message and use IOwinContext.Authentication: https://msdn.microsoft.com/en-us/library/system.net.http.owinhttprequestmessageextensions.getowincontext(v=vs.118).aspx
var context = Request.GetOwinContext();
var result = await context.Authentication.AuthenticateAsync(OAuthDefaults.AuthenticationType);
if (result == null) {
throw new InvalidOperationException();
}
var properties = result.Properties;
(of course, you also need to have a properly configured app.UseOAuthBearerAuthentication call in your Startup class, but I assume it's the case here).

Related

How to implement Exchange online OAuth2.0 for unmanaged EWS API?

For managed EWS code, I have used to OAuth 2.0 to get token and it worked.
For unmanaged EWS, it is failing to connect to Exchange as an unauthorized error.
Below is the code to access unmanaged EWS.
How to make below code work with OAuth token instead of passing credentials as below?.
Binding = new ExchangeServiceBinding
{
Url = ServerUrl,
Credentials = new OAuthCredentials(token),
RequestServerVersionValue = new RequestServerVersion { Version = ExchangeVersionType.Exchange2007_SP1 },
ExchangeImpersonation = null
};
Above is not working as credential is asking of type ICredentials and it is not accepting token. Please help me.
Below is the code how I direct access managed EWS.
var authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, credential.UserName, credential.SecurePassword).ExecuteAsync();
configure the ExchangeService with the access token
ExchangeService = new ExchangeService();
ExchangeService.Url = new Uri(ServerUrl);
ExchangeService.Credentials = new OAuthCredentials(authResult.AccessToken);
One method i use (as I've never worked out how to override the WSDL classes) is if you modify the Reference.cs file that gets generated in the web references directory you can modify the GetWebResponse command (In this case the token is being passed via the credentials object password property but there a number of different approaches you can take here) eg
private String AnchorMailbox;
private bool oAuth;
protected override System.Net.WebResponse GetWebResponse(System.Net.WebRequest req)
{
if (xAnchorMailbox != null)
{
if (xAnchorMailbox != "")
{
req.Headers.Add("X-AnchorMailbox", AnchorMailbox);
}
}
if(req.Credentials is System.Net.NetworkCredential)
{
if(oAuth){
req.Headers.Add("Authorization", ("Bearer " + ((System.Net.NetworkCredential)req.Credentials).Password));
}
}
System.Net.HttpWebResponse
rep = (System.Net.HttpWebResponse)base.GetWebResponse(req);
return rep;
}

Web API authentication - returning the same OAUTH refresh token

I am pretty new to this.. so any help would be greatly appreciated.
I have a WebApi service that uses OAUTH token and refresh token authentication.
All works well at the moment:
step1: I send in the user and password and it generates an authentication token and a refresh token. The refresh token is saved in the DB.
step2. I can now use the refresh token and i receive the authentication token and a new refresh token. I want a way to use the same refresh token i sent and not reuse a new one.
This is my code for the refresh token:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
RefreshTokensRepository _repo = new RefreshTokensRepository();
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
//HERE I regenerate the token, but I have no idea how to retrieve the already sent one.
var refreshTokenId = Guid.NewGuid().ToString("n");
//saving in BD:
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var token = new RefreshTokens()
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime));
token.ProtectedTicket = context.SerializeTicket();
var result = _repo.Add(token);
if(!string.IsNullOrEmpty(result))
context.SetToken(refreshTokenId);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
string hashedTokenId = Helper.GetHash(context.Token);
RefreshTokensRepository _repo = new RefreshTokensRepository();
var refreshToken = _repo.FindById(hashedTokenId);
if (refreshToken != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
_repo.Remove(hashedTokenId);
}
}
void IAuthenticationTokenProvider.Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
void IAuthenticationTokenProvider.Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
My code is based on this samples:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
I would like to use the same sent refresh token, but I have no idea how to use the already sent one in this context.
Any ideas?
Disclaimer: I don't condone reusing refresh tokens.
However, this does provide a good opportunity for everyone to improve knowledge of how this process works and there could be a good reason for reusing past refresh tokens in certain scenarios. I'm basing my answer upon:
Question: "I want a way to use the same refresh token i sent and not reuse a new one."
Code comment, "//HERE I regenerate the token, but I have no idea how to retrieve the already sent one."
PseudoCode Steps:
Store a user identifier as a property in AuthenticationProperties in the GrantResourceOwnerCredentials() method. From the sample code, it looks like you may already be doing this with "userName":
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},{
"userName", context.UserName
}
});
Retrieve the user identifier in the CreateAsync() method of your IAuthenticationTokenProvider implementation (e.g. "SimpleRefreshTokenProvider" in your case). This would look something like:
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var userName = context.Ticket.Properties.Dictionary["userName"];
...
Still in the CreateAsync() method use the user identifier to lookup the existing refresh token. This would look something like:
var existingRefreshToken = await _repo.FindRefreshTokenByUserNameAsync(userName);
Note: You would need to write the above method into your AuthRepository class from the example code. The "FindRefreshTokenByUserNameAsync(userName) implementation might include something like this if you're using Entity Framework and have a "RefreshToken" table that is being used to persist the granted refresh token:
var existingToken = RefreshToken.Where(r => r.UserName == userName).SingleOrDefault();
At this point, you have the existing token and should be able to re-use that refresh token value instead of Guid.NewGuid():
var refreshTokenId = existingToken.Token;
Taking a look at the tutorial's example code, however, indicates that a HashAlgorithm is being used to store the refresh token's value. That could complicate things a bit for you as storing a hash value is better security, but the process of hashing here is meant to be one-way.
If you really want to reuse the original token value when all you have persisted is the hashed token, would need to implement code that captures the non-hashed token value in the ReceiveAsync() method. It would have to temporarily persist the non-hashed value long enough for you to use it in the CreateAsync() method. In other words, you would have to save/persist the "context.Token" in ReceiveAsync(), associate it with your userName (from context.Ticket.Properties.Dictionary["userName"]), and use it later in the CreateAsync() method. It's hacky and I don't like it, but you would do it around this line of code in ReceiveAsync():
string hashedTokenId = Helper.GetHash(context.Token);

Aspnet core web api protected with Azure

I have a web api in my organization built with aspnet core. We want to publish that api to be consumed by an android app, a mvc5 app and an aspnet core mvc6 app. How can I configure the web api in azure so that the apps that consume it don't ask to login. The web apps, are already protected with azure, but when I protect the web api with azure I get a 401 when I make a request to it. I don't know how to configure the app in azure or the code I must configure in the api. I've read a lot but I don't find a way to acomplish this. All I want is to login in my web app, and the web app starts to ask data to the web api through ajax. I should send in the ajax request some sort of bareer token, but i don`t know what config i must do in azure and in the apps. I hope you can help me.
After you protected the web API with Azure AD, we need to send to access token with request for the web API for authorization. And we can get the access token when the users call the web API from web app. Here is the code to acquire the token in the web app for your reference:
public async Task<IActionResult> Index()
{
AuthenticationResult result = null;
List<TodoItem> itemList = new List<TodoItem>();
try
{
string userObjectID = (User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
ClientCredential credential = new ClientCredential(Startup.ClientId, Startup.ClientSecret);
result = await authContext.AcquireTokenSilentAsync(Startup.TodoListResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
//
// Retrieve the user's To Do List.
//
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, TodoListBaseAddress + "/api/todolist");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
//
// Return the To Do List in the view.
//
if (response.IsSuccessStatusCode)
{
List<Dictionary<String, String>> responseElements = new List<Dictionary<String, String>>();
JsonSerializerSettings settings = new JsonSerializerSettings();
String responseString = await response.Content.ReadAsStringAsync();
responseElements = JsonConvert.DeserializeObject<List<Dictionary<String, String>>>(responseString, settings);
foreach (Dictionary<String, String> responseElement in responseElements)
{
TodoItem newItem = new TodoItem();
newItem.Title = responseElement["title"];
newItem.Owner = responseElement["owner"];
itemList.Add(newItem);
}
return View(itemList);
}
else
{
//
// If the call failed with access denied, then drop the current access token from the cache,
// and show the user an error indicating they might need to sign-in again.
//
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
var todoTokens = authContext.TokenCache.ReadItems().Where(a => a.Resource == Startup.TodoListResourceId);
foreach (TokenCacheItem tci in todoTokens)
authContext.TokenCache.DeleteItem(tci);
ViewBag.ErrorMessage = "UnexpectedError";
TodoItem newItem = new TodoItem();
newItem.Title = "(No items in list)";
itemList.Add(newItem);
return View(itemList);
}
}
}
catch (Exception ee)
{
if (HttpContext.Request.Query["reauth"] == "True")
{
//
// Send an OpenID Connect sign-in request to get a new set of tokens.
// If the user still has a valid session with Azure AD, they will not be prompted for their credentials.
// The OpenID Connect middleware will return to this controller after the sign-in response has been handled.
//
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme);
}
//
// The user needs to re-authorize. Show them a message to that effect.
//
TodoItem newItem = new TodoItem();
newItem.Title = "(Sign-in required to view to do list.)";
itemList.Add(newItem);
ViewBag.ErrorMessage = "AuthorizationRequired";
return View(itemList);
}
//
// If the call failed for any other reason, show the user an error.
//
return View("Error");
}
And below is the code sample which use JwtBearerAppBuilderExtensions to add OpenIdConnect Bearer authentication capabilities to an HTTP application pipeline for the web API to verify the token:
public class Startup
{
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add the console logger.
loggerFactory.AddConsole(LogLevel.Debug);
// Configure the app to use Jwt Bearer Authentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
});
}
}
The full code sample you can refer here.
Note: to run this sample successfully, we need to modify the Title and Owner to lowercase title, owner in the ToDoController of web app:
foreach (Dictionary<String, String> responseElement in responseElements)
{
TodoItem newItem = new TodoItem();
newItem.Title = responseElement["title"];
newItem.Owner = responseElement["owner"];
itemList.Add(newItem);
}
You can use Azure OpenIdConnect for federated authentication. A good article from microsoft below -
Calling a web API in a web app using Azure AD and OpenID Connect

OAuth - Read the generated Access token and add cookie in response

I am using OAuth in ASP.NET Web Api to return access token to the caller of the application.
I have inherited my OAuth provider class from OAuthAuthorizationServerProvider and once the user is authenticated inside the GrantResourceOwnerCredentials function, I want to read the generated access token, create it's hash with some salt value and then add the created hash into a cookie.
Below is the simplified definition of my GrantResourceOwnerCredentials function.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager, CookieAuthenticationDefaults.AuthenticationType);
//Add claims required on client side.
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
//Generate the token behind the scene for given ticket
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
SetCsrfCookie(context);
}
private void SetCsrfCookie(OAuthGrantResourceOwnerCredentialsContext context)
{
var accessToken = "<READ THE GENERATED ACCESS TOKEN HERE>"; //<------ How?
if(string.IsNullOrEmpty(accessToken)) return;
var csrfToken = Helper.GetHash(accessToken);
context.Response.Cookies.Append("XSRF-TOKEN", csrfToken, new CookieOptions {HttpOnly = false});
}
I am facing two issues here.
First one is how to read the generated access token in the SetCsrfCookie function in the code above.
Generated cookie is not received on the client side.
I know its possible to intercept the response in a some OwinMiddleware inherited class and then I may be able to generate the required cookie and attach to the response but first I have not tried that and secondly, it seems better option to handle this case inside my OAuth provider class as some people suggest that deriving from the OwinMiddleware is not a good practice.
I finally managed to fix the cookie issue by adding the below line of code on angular side
$httpProvider.defaults.withCredentials = true;
On the Web Api side I just set the Access-Control-Allow-Credentials response header to true inside the WebApiConfig.Register method like below:
var cors = new EnableCorsAttribute(ConfigurationManager.AppSettings["ALLOWED_ORIGIN"], "*", "*")
{
SupportsCredentials = true
};
config.EnableCors(cors);
This solved my cookie problem.
For accessing the generated access token I inherited a class from OwinMiddleware and inside the Invoke function I access the response body to read the access token like below:
public override async Task Invoke(IOwinContext context)
{
var path = context.Request.Path;
var stream = context.Response.Body;
var buffer = new MemoryStream();
context.Response.Body = buffer;
await Next.Invoke(context);
var reqStream = new StreamReader(context.Request.Body);
reqStream.BaseStream.Position = 0;
var data = reqStream.ReadToEnd();
if (path.Equals(new PathString("/token"),StringComparison.CurrentCultureIgnoreCase))
{
buffer.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(buffer);
var responseBody = await reader.ReadToEndAsync();
//check if the response body contains access token if so then do your processing
}
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(stream);
}

ASP.NET Web API - Passing a UserToken(string) to a LoginController using a DelegatingHandler

I have an ASP.NET Web API project and on initial user login, the username and password are sent in an http header over SSL and validated by the server.
The server creates a database record with the UserId, a randmon 64 character string (UserToken), expiration date and the client IP address.
The UserToken is then sent back to the client and then be stored in a cookie.
All subsequent requests send the UserToken in an http header and that is validated using the calling IP address by the server.
This way, the username and password are only sent once, and all calls using the UserToken are logged.
I have created two custom DelegatingHandlers - LoginAuthenticationHandler, and TokenAuthenticationHandler - which process the http headers and send an appropriate 200 or 400 http response.
////////////////
Seems my only problem is that I want the LoginAuthenticationHandler to also return the UserToken to the client, so it can store the cookie.
Sorry for the verbosity :-\
Also - I'm new to Web API - so maybe this is not the best place for this to be done - but it would be very convenient if the UserToken can be passed back to the LoginController in this way.
Thanks for any input :-)
Some related SO posts:
DelegatingHandler for response in WebApi
Is it possible to pass data from DelegatingHandler to Controller in ASP.NET Web API?
////////////////
public class LoginAuthenticationHandler : DelegatingHandler
{
public const string BasicScheme = "Basic";
public const string ChallengeAuthenticationHeaderName = "WWW-Authenticate";
public const char AuthorizationHeaderSeparator = ':';
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Get Authorization Http Header
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// Check if Basic Authentication
if (authHeader.Scheme != BasicScheme)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// Decode UserName + Password from Http Header
var encodedCredentials = authHeader.Parameter;
var credentialBytes = Convert.FromBase64String(encodedCredentials);
var credentials = Encoding.ASCII.GetString(credentialBytes);
var credentialParts = credentials.Split(AuthorizationHeaderSeparator);
if (credentialParts.Length != 2)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
var username = credentialParts[0].Trim();
var password = credentialParts[1].Trim();
// Authenticate Username + Password and Return UserToken
var userId = new Users().GetUserIdFromUserNamePassword(username, password);
if (userId == 0)
{
// Unauthorized
return CreateUnauthorizedResponse();
}
// User is Authorized - Create New UserToken
var ipAddress = HttpContext.Current.Request.UserHostAddress;
var userToken = new Users().CreateUserToken(ipAddress, userId);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
//======================================================
// Return UserToken to Login Controller to be Stored as Cookie on the Client
// response.Content = userToken ??
// maybe set header for userToken ??
// HttpRequestMessage Properties ??
return response;
//======================================================
});
}
private static Task<HttpResponseMessage> CreateUnauthorizedResponse()
{
// Send Back Http Unauthorized if Authentication Fails
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.Add(ChallengeAuthenticationHeaderName, BasicScheme);
var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
taskCompletionSource.SetResult(response);
return taskCompletionSource.Task;
}
}
}
Generally, HTTP services are stateless and the concept of login does not apply. LoginController is for the MVC controllers and not web API. What you are trying to do is not a good practice, even though it is technically possible to achieve.
If you really want to do what you are trying to do, do not think along the lines of sending the session data (what you call the user token) to LoginController. You can write the cookie into the response from your message handler itself. See this. You must only store encrypted data into a cookie in that case. Instead of creating your own cookie and all that, you can use Forms Authentication and create a cookie with FA ticket. See this.
BTW, it is possible and easy to spoof client IP addresses.
Perhaps you could login using the controller without using a DelegatingHandler: you could return the token to the client to be added to the header of future API calls, or add it to the header in the controller using the Request.Headers.Add function.
Then you would not need two custom DelegatingHandlers, the TokenAuthenticationHandler would be sufficient. But you would want to specify that all requests other than the initial login are funneled through the TokenAuthenticationHandler.
To do that, you will need to customize the WebAPI routes. In the default Web API projects, this is currently done in the WebApiConfig.Register method in WebApiConfig.cs (called from Global.asax.cs). First, have all your API calls route through your TokenAuthenticationHandler; then add the login route plainly such that it does not funnel through your TokenAuthenticationHandler:
//this message handler chain is used to put TokenAuthenticationHandleron all API requests and not Login
DelegatingHandler[] handlers = new DelegatingHandler[] {
new TokenAuthenticationHandler()
};
var routeHandlers = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(config), handlers);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}",
defaults: null,
constraints: null,
handler: routeHandlers
);
//login route
config.Routes.MapHttpRoute(
name: "Login",
routeTemplate: "login/{action}",
defaults: new { Controller = "Login" }
);
Now, you can validate the token in the TokenAuthenticationHandler using request.Headers.TryGetValues to get it:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
//token validation
IEnumerable<string> foundValues = null;
if (request.Headers.TryGetValues("AuthenticationToken", out foundValues))
{
if (foundValues.Count() == 1)
{
string token = foundValues.Single();
AuthenticationDAO dao = new AuthenticationDAO();
if (dao.AuthenticateUser(token))
{
//add values to request.Properties for use in Web API controllers
request.Properties.Add(new KeyValuePair<string, object>("SomeValue", 4));
//Engage!
return base.SendAsync(request, cancellationToken);
}
}
}
//fail if token not present or not valid
var tcs = new TaskCompletionSource<HttpResponseMessage>();
tcs.SetResult(new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent("Missing or invalid authorization token.")
});
return tcs.Task;
}
As per your original question of passing values from the DelegatingHandler to the Controller, that is easily possible using the request.Properties.Add function as demonstrated above.
Some additional considerations:
I am not sure that sending the login credentials in the header is any
more secure than just as content in the request, since it is all over
SSL.
You should consider implementing an AntiForgeryToken. This
article is a good starter, and this SO post points out how
you could use DelegatingHandler to also only check for it on web
requests (allowing your api to be accessed from native apps).
You can easily add a DelegatingHandler that applies to all requests
that enforces HTTPS.
Hope that helps. What I've outlined is the way I'm doing it, so I hope for some comments if it's wrong.

Resources