Accepted answer note:
Although I have appreciated the help of creating my own OwinMiddleware to send images after doing some checks instead of IHttpModule, that doesn't solve the issue entirely.
The thing is I have added an Authorization header to the ajax requests, and inside that header I am sending my Bearer's Token so that I can get logged user information from Owin. So I have to add this header to the image requests either, to be able to get logged user information from image handler middleware.
Original Question:
I am following this blog post to create token based authentication for my web project. Because some resources of my Web API will be used by native mobile clients. And I have heard that token based authentication is the way to go for that. And in my own project I have a custom image request handler. And need the logged user information inside this handler. But when i try to extract user information from ticket I get null. And I am not sure about this but, I think I have 2 different IIdentity objects here, and I need the one stored inside Owin Context.
Here let me show you some codes;
My GrantResourceOwnerCredentials which is storing claims into ClaimsIdentity,
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
....
// checking user credentials and get user information into 'usr' variable
....
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Sid, usr.UserId.ToString()));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userId", usr.UserId.ToString()
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
Helper function to extract user id from the given IIdentity object
public class utils {
public Guid? GetUserIdFromTicket(IIdentity identity)
{
var cId = (ClaimsIdentity)identity;
var uid = cId.FindFirst(ClaimTypes.Sid);
if (uid != null && Comb.IsComb(uid.Value))
return new Guid(uid.Value);
else
return null;
}
....
}
Now I can get the loggedUserId from my controller like,
var loggedUserId = utils.GetUserIdFromTicket(User.Identity);
but if I call it from my IHttpHandler I get null,
public class ImageHandler : IHttpHandler
{
public ImageHandler()
{
}
public ImageHandler(RequestContext requestContext)
{
RequestContext = requestContext;
}
protected RequestContext RequestContext { get; set; }
public utils utils = new utils(); // changed name for simplicity.
public void ProcessRequest(HttpContext context)
{
var strUserId = RequestContext.RouteData.Values["userid"].ToString();
var strContentId = RequestContext.RouteData.Values["contentid"].ToString();
var fileName = RequestContext.RouteData.Values["filename"].ToString();
var size = RequestContext.RouteData.Values["size"].ToString();
var loggedUserId = utils.GetUserIdFromTicket(context.User.Identity);
....
image processing
....
context.Response.End();
}
}
Hope I didn't messed this up for good...
Solution:
I have implemented my own middleware to serv images to my users after doing some checks. Here is my Invoke task implementation. Everything else is just like as recommended in accepted answer. But as stated above, for this to work I have to send images with the Authorization header, or the loggedUserId will be null again.
public async override Task Invoke(IOwinContext context)
{
// need to interrupt image requests having src format : http://www.mywebsite.com/myapp-img/{userid}/{contentId}/{fileName}/{size}/
if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("myapp-img") > -1)
{
// get values from url.
var pathValues = context.Request.Path.Value.Split('/');
var strUserId = pathValues[2].ToString();
var strContentId = pathValues[3].ToString();
var fileName = pathValues[4].ToString();
var size = pathValues[5].ToString();
// check if code returned a notfound or unauthorized image as response.
var hasError = false;
// get userId from static utils class providing current owin identity object
var loggedUserId = ChildOnBlogUtils.GetUserIdFromTicket(context.Request.User.Identity);
// save root path of application to provide error images.
var rootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
// assign content type of response to requested file type
context.Response.ContentType = ChildOnBlogUtils.GetContentType(context.Request.Path.Value.ToString());
// if user requested thumbnail send it without doing checks
if (size == "thumb")
{
imgPath = "images/" + strUserId.ToLower() + "/thumbnail/" + fileName;
}
else
{
var canSee = false;
// check if user can see the content and put the result into canSee variable
// I am using loggedUserId inside these checks
...
...
// end checks
if (canSee)
{
// removed some more checks here for simplicity
imgPath = "images/" + strUserId.ToLower() + "/" + fileName;
}
else
{
context.Response.ContentType = "Image/png";
var imgData = File.ReadAllBytes(rootPath + "/images/unauthorized.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
hasError = true;
}
}
if (!hasError) // if no errors have been risen until this point. try to provide the requested image to user.
{
try
{
var imgData = UserMediaContainer.GetFileContent(imgPath); // get file from storage account (azure)
if (imgData.Length == 0)
{
context.Response.ContentType = "Image/png";
imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
else
{
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
}
catch (Exception ex)
{
context.Response.ContentType = "Image/png";
var imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
}
}
}
else if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("profile-img") > -1)
{
// profile image provider. Same code as providing thumbnails.
}
else
{
// if it is not an image request to be handled. move to the next middleware.
await Next.Invoke(context);
}
}
I guess your ImageHandler is processed before everything else in the owin pipeline, which means it is processed before the authorization comes into place.
Since you're using owin I would advise you to drop the IHttpHandler and use some custom owin middleware.
Following this path will allow you to inject your module in the right place in the pipeline.
Creating the middleware is quite easy:
public class ImageProcessingMiddleware : OwinMiddleware
{
public ImageProcessingMiddleware(OwinMiddleware next): base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
string username = context.Request.User.Identity.Name;
Console.WriteLine("Begin Request");
await Next.Invoke(context);
Console.WriteLine("End Request");
}
}
Once you have defined your middleware you can create an extension method for the instantiation:
public static class ImageProcessingExtensions
{
public static IAppBuilder UseImageProcessing(this IAppBuilder app)
{
return app.Use<ImageProcessingMiddleware>();
}
}
Now you can plug-in your middleware in the pipeline:
app.UseImageProcessing();
If you have followed Taiseer sample, you would do that after you have configured the authorization module:
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
Going back to the middleware, you might have noticed there's a method called Invoke:
public async override Task Invoke(IOwinContext context)
{
string username = context.Request.User.Identity.Name;
Console.WriteLine("Begin Request");
await Next.Invoke(context);
Console.WriteLine("End Request");
}
This is the entry-point of each middleware. As you can see I am reading the user's name authorized right after the authorization token has been verified and authorized.
There's an interesting article about owin middleware which is worth reading.
Related
I am quite new in web API implementation, I have created a web API service to use it with ASP.net web form applications as well as some stand alone applications(C# Console/Windows application) using HttpClient object.
I have implemented a basic JWT access token authentication with expiration time limit in web api, this authentication technique is working fine until token not expired, when token get expired web api does not accept request as token has expired! which is fine as per authentication implementation, but I want to implement refresh token logic in web api so token can renew/refersh and client should be able to use the web api resource.
I googled a lot but unable to find the proper implementation of refresh token logic. Please help me if any one has right approach to handle the expired access token.
Following are the steps that I have followed to use the web api in asp.net application.
In ASP.net web form login page I called the web API "TokenController" this controller take two arguments loginID and password and return the JWT token that I stored in session object.
Now whenever my client application need too use the web api resource has to send the access token in request header while making call to web api using httpclient.
But when token get expired client unable use the web api resource he has to login again and renew the token! this I don't want, user should not prompt to be login again as application session out time not elapsed yet.
How do I refresh the token without forcing user to login again.
If my given below JWT access token implementation logic is not suitable or it is incorrect, please let me know the correct way.
Following is the code.
WebAPI
AuthHandler.cs
public class AuthHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage errorResponse = null;
try
{
IEnumerable<string> authHeaderValues;
request.Headers.TryGetValues("Authorization", out authHeaderValues);
if (authHeaderValues == null)
return base.SendAsync(request, cancellationToken);
var requestToken = authHeaderValues.ElementAt(0);
var token = "";
if (requestToken.StartsWith("Bearer ", StringComparison.CurrentCultureIgnoreCase))
{
token = requestToken.Substring("Bearer ".Length);
}
var secret = "w$e$#*az";
ClaimsPrincipal cp = ValidateToken(token, secret, true);
Thread.CurrentPrincipal = cp;
if (HttpContext.Current != null)
{
Thread.CurrentPrincipal = cp;
HttpContext.Current.User = cp;
}
}
catch (SignatureVerificationException ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message);
}
catch (Exception ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
}
return errorResponse != null
? Task.FromResult(errorResponse)
: base.SendAsync(request, cancellationToken);
}
private static ClaimsPrincipal ValidateToken(string token, string secret, bool checkExpiration)
{
var jsonSerializer = new JavaScriptSerializer();
string payloadJson = string.Empty;
try
{
payloadJson = JsonWebToken.Decode(token, secret);
}
catch (Exception)
{
throw new SignatureVerificationException("Unauthorized access!");
}
var payloadData = jsonSerializer.Deserialize<Dictionary<string, object>>(payloadJson);
object exp;
if (payloadData != null && (checkExpiration && payloadData.TryGetValue("exp", out exp)))
{
var validTo = AuthFactory.FromUnixTime(long.Parse(exp.ToString()));
if (DateTime.Compare(validTo, DateTime.UtcNow) <= 0)
{
throw new SignatureVerificationException("Token is expired!");
}
}
var clmsIdentity = new ClaimsIdentity("Federation", ClaimTypes.Name, ClaimTypes.Role);
var claims = new List<Claim>();
if (payloadData != null)
foreach (var pair in payloadData)
{
var claimType = pair.Key;
var source = pair.Value as ArrayList;
if (source != null)
{
claims.AddRange(from object item in source
select new Claim(claimType, item.ToString(), ClaimValueTypes.String));
continue;
}
switch (pair.Key.ToUpper())
{
case "USERNAME":
claims.Add(new Claim(ClaimTypes.Name, pair.Value.ToString(), ClaimValueTypes.String));
break;
case "EMAILID":
claims.Add(new Claim(ClaimTypes.Email, pair.Value.ToString(), ClaimValueTypes.Email));
break;
case "USERID":
claims.Add(new Claim(ClaimTypes.UserData, pair.Value.ToString(), ClaimValueTypes.Integer));
break;
default:
claims.Add(new Claim(claimType, pair.Value.ToString(), ClaimValueTypes.String));
break;
}
}
clmsIdentity.AddClaims(claims);
ClaimsPrincipal cp = new ClaimsPrincipal(clmsIdentity);
return cp;
}
}
AuthFactory.cs
public static class AuthFactory
{
internal static DateTime FromUnixTime(double unixTime)
{
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return epoch.AddSeconds(unixTime);
}
internal static string CreateToken(User user, string loginID, out double issuedAt, out double expiryAt)
{
var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
expiryAt = Math.Round((DateTime.UtcNow.AddMinutes(TokenLifeDuration) - unixEpoch).TotalSeconds);
issuedAt = Math.Round((DateTime.UtcNow - unixEpoch).TotalSeconds);
var payload = new Dictionary<string, object>
{
{enmUserIdentity.UserName.ToString(), user.Name},
{enmUserIdentity.EmailID.ToString(), user.Email},
{enmUserIdentity.UserID.ToString(), user.UserID},
{enmUserIdentity.LoginID.ToString(), loginID}
,{"iat", issuedAt}
,{"exp", expiryAt}
};
var secret = "w$e$#*az";
var token = JsonWebToken.Encode(payload, secret, JwtHashAlgorithm.HS256);
return token;
}
public static int TokenLifeDuration
{
get
{
int tokenLifeDuration = 20; // in minuets
return tokenLifeDuration;
}
}
internal static string CreateMasterToken(int userID, string loginID)
{
var payload = new Dictionary<string, object>
{
{enmUserIdentity.LoginID.ToString(), loginID},
{enmUserIdentity.UserID.ToString(), userID},
{"instanceid", DateTime.Now.ToFileTime()}
};
var secret = "w$e$#*az";
var token = JsonWebToken.Encode(payload, secret, JwtHashAlgorithm.HS256);
return token;
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.MessageHandlers.Add(new AuthHandler());
}
}
TokenController .cs
public class TokenController : ApiController
{
[AllowAnonymous]
[Route("signin")]
[HttpPost]
public HttpResponseMessage Login(Login model)
{
HttpResponseMessage response = null;
DataTable dtblLogin = null;
double issuedAt;
double expiryAt;
if (ModelState.IsValid)
{
dtblLogin = LoginManager.GetUserLoginDetails(model.LoginID, model.Password, true);
if (dtblLogin == null || dtblLogin.Rows.Count == 0)
{
response = Request.CreateResponse(HttpStatusCode.NotFound);
}
else
{
User loggedInUser = new User();
loggedInUser.UserID = Convert.ToInt32(dtblLogin.Rows[0]["UserID"]);
loggedInUser.Email = Convert.ToString(dtblLogin.Rows[0]["UserEmailID"]);
loggedInUser.Name = Convert.ToString(dtblLogin.Rows[0]["LastName"]) + " " + Convert.ToString(dtblLogin.Rows[0]["FirstName"]);
string token = AuthFactory.CreateToken(loggedInUser, model.LoginID, out issuedAt, out expiryAt);
loggedInUser.Token = token;
response = Request.CreateResponse(loggedInUser);
}
}
else
{
response = Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
return response;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
}
PremiumCalculatorController.cs
PremiumCalculatorController : ApiController
{
[HttpPost]
public IHttpActionResult CalculatAnnualPremium(PremiumFactorInfo premiumFactDetails)
{
PremiumInfo result;
result = AnnualPremium.GetPremium(premiumFactDetails);
return Ok(result);
}
}
Web Form Application
Login.aspx.cs
public class Login
{
protected void imgbtnLogin_Click(object sender, System.EventArgs s)
{
UserInfo loggedinUser = LoginManager.ValidateUser(txtUserID.text.trim(), txtPassword.text);
if (loggedinUser != null)
{
byte[] password = LoginManager.EncryptPassword(txtPassword.text);
APIToken tokenInfo = ApiLoginManager.Login(txtUserID.text.trim(), password);
loggedinUser.AccessToken = tokenInfo.Token;
Session.Add("LoggedInUser", loggedinUser);
Response.Redirect("Home.aspx");
}
else
{
msg.Show("Logn ID or Password is invalid.");
}
}
}
ApiLoginManager.cs
public class ApiLoginManager
{
public UserDetails Login(string userName, byte[] password)
{
APIToken result = null;
UserLogin objLoginInfo;
string webAPIBaseURL = "http://localhost/polwebapiService/"
try
{
using (var client = new HttpClient())
{
result = new UserDetails();
client.BaseAddress = new Uri(webAPIBaseURL);
objLoginInfo = new UserLogin { LoginID = userName, Password = password };
var response = client.PostAsJsonAsync("api/token/Login", objLoginInfo);
if (response.Result.IsSuccessStatusCode)
{
string jsonResponce = response.Result.Content.ReadAsStringAsync().Result;
result = JsonConvert.DeserializeObject<APIToken>(jsonResponce);
}
response = null;
}
return result;
}
catch (Exception ex)
{
throw ex;
}
}
}
AnnualPremiumCalculator.aspx.cs
public class AnnualPremiumCalculator
{
protected void imgbtnCalculatePremium_Click(object sender, System.EventArgs s)
{
string token = ((UserInfo)Session["LoggedInUser"]).AccessToken;
PremiumFactors premiumFacts = CollectUserInputPremiumFactors();
PremiumInfo premiumDet = CalculatePremium(premiumFacts, token);
txtAnnulPremium.text = premiumDet.Premium;
//other details so on
}
public PremiumInfo CalculatePremium(PremiumFactors premiumFacts, string accessToken)
{
PremiumInfo result = null;
string webAPIBaseURL = "http://localhost/polwebapiService/";
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(webAPIBaseURL);
StringContent content = new StringContent(JsonConvert.SerializeObject(premiumFacts), Encoding.UTF8, "application/json");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = client.PostAsync("api/calculators/PremiumCalculator", content);
if (response.Result.IsSuccessStatusCode)
{
string jsonResponce = response.Result.Content.ReadAsStringAsync().Result;
result = JsonConvert.DeserializeObject<PremiumInfo>(jsonResponce);
}
response = null;
}
return result;
}
finally
{
}
}
}
above is a sample code to illustrate the issue, it may have some typo.
I have some remarks:
The access token is meant to be saved by the client and not in a session on the server. The same counts for the refresh token. The reason for that is, that there usually is no session. Smart clients can handle the token without session, MVC websites can use a cookie and the API doesn't know sessions. It is not forbidden, but then again you'll need to worry about session expiration and all users have to login again when you restart your server.
If you want to implement OAuth then read the specification. In there you will find everything you'll need to implement the refresh token.
In TokenController you handle the login. There you should check other conditions as well.
grant_type = password
Content-Type has to be "application/x-www-form-urlencoded"
the request should only be handled if send over a secured line (https).
When the access_token is obtained and only if the refresh_token is requested, you should include the refresh_token in the access_token.
You don't need a refresh token for client applications (grant_type = client_credentials) as those use a clientid / secret to obtain an access token. Extend TokenController to allow the client_credentials flow. Please note: refresh tokens are for users only and should be used only if they can be kept secret. A refresh token is very powerfull, so handle with care.
In order to refresh an access token you'll need to send the refresh token to the endpoint. In your case you can extend the TokenController to allow a refresh_token request. You'll need to check:
grant_type = refresh_token
Content-Type has to be "application/x-www-form-urlencoded"
There are several scenarios for the refresh token, which you can also combine:
Save the refresh token in a database. Each time a refresh token is used you can remove it from the database, then save the new refresh token which is also returned in the new access_token.
Set the refresh token to a longer lifetime and do not refresh it when the access token is refreshed. In this case the returned access_token does not include a new refresh token. That way you'll need to login again after the refresh_token expires.
Please note, a refresh token that never expires and cannot be revoked gives a user unlimited access, so be carefull with your implementation.
In my answer here you can see how a refresh token can be handled using Identity 2. You can consider to switch to Identity 2.
I think I've mentioned everything. Please let me know if I missed something or if something isn't clear.
This can be done with a separate persisting refresh token. A nice tutorial at http://www.c-sharpcorner.com/article/handle-refresh-token-using-asp-net-core-2-0-and-json-web-token/
I am new to asp.net core and web API 2 and trying to implement basic authentication in web API like request headers contains the username and password and i will verify them. I have implemented the same thing in Web API using action filters. Now my question is that is it possible to implement basic authentication in web API 2? If possible then can I achieve it with action filters and how? What is the best way to authentication in Web API 2? Should i use middle ware?
I'm not sure why you said ASP.Net Core and Web API 2. They are mutually exclusive; we don't normally use both in same project.
Now my question is that is it possible to implement basic
authentication in web API 2? If possible then can I achieve it with
action filters and how? What is the best way to authentication in Web
API 2? Should i use middle ware?
In Web API 2, you can easily implement Basic Authentication using DelegatingHandler.
Here is the sample code -
IBasicSecurityService
public interface IBasicSecurityService
{
bool SetPrincipal(string username, string password);
}
BasicSecurityService
public class BasicSecurityService : IBasicSecurityService
{
public bool SetPrincipal(string username, string password)
{
// Get user from database
var user = GetUser(username);
IPrincipal principal = null;
if (user == null || (principal = GetPrincipal(user)) == null)
{
// System could not validate user
return false;
}
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
return true;
}
public virtual IPrincipal GetPrincipal(User user)
{
var identity = new GenericIdentity(user.Username, Constants.SchemeTypes.Basic);
identity.AddClaim(new Claim(ClaimTypes.GivenName, user.Firstname));
identity.AddClaim(new Claim(ClaimTypes.Surname, user.Lastname));
// Get authroized roles and add them as Role Claim.
identity.AddClaim(new Claim(ClaimTypes.Role, "Manager"));
return new ClaimsPrincipal(identity);
}
}
BasicAuthenticationMessageHandler
public class BasicAuthenticationMessageHandler : DelegatingHandler
{
public const char AuthorizationHeaderSeparator = ':';
private const int UsernameIndex = 0;
private const int PasswordIndex = 1;
private const int ExpectedCredentialCount = 2;
private readonly IBasicSecurityService _basicSecurityService;
public BasicAuthenticationMessageHandler(IBasicSecurityService basicSecurityService)
{
_basicSecurityService = basicSecurityService;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// Already authenticated; passing on to next handler...
return await base.SendAsync(request, cancellationToken);
}
if (!CanHandleAuthentication(request))
{
// Not a basic auth request; passing on to next handler...
return await base.SendAsync(request, cancellationToken);
}
bool isAuthenticated;
try
{
isAuthenticated = Authenticate(request);
}
catch (Exception e)
{
// Failure in auth processing
return CreateUnauthorizedResponse();
}
if (isAuthenticated)
{
var response = await base.SendAsync(request, cancellationToken);
return response;
}
return CreateUnauthorizedResponse();
}
public bool CanHandleAuthentication(HttpRequestMessage request)
{
return (request.Headers != null
&& request.Headers.Authorization != null
&& request.Headers.Authorization.Scheme.ToLowerInvariant() == Constants.SchemeTypes.Basic);
}
public bool Authenticate(HttpRequestMessage request)
{
// Attempting to authenticate...
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
return false;
}
var credentialParts = GetCredentialParts(authHeader);
if (credentialParts.Length != ExpectedCredentialCount)
{
return false;
}
return _basicSecurityService.SetPrincipal(credentialParts[UsernameIndex], credentialParts[PasswordIndex]);
}
public string[] GetCredentialParts(AuthenticationHeaderValue authHeader)
{
var encodedCredentials = authHeader.Parameter;
var credentialBytes = Convert.FromBase64String(encodedCredentials);
var credentials = Encoding.ASCII.GetString(credentialBytes);
var credentialParts = credentials.Split(AuthorizationHeaderSeparator);
return credentialParts;
}
public HttpResponseMessage CreateUnauthorizedResponse()
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Constants.SchemeTypes.Basic));
return response;
}
}
Credit: Page 121 of ASP.NET Web API 2: Building a REST Service from Start to Finish.
I'm writing my first Web API 2. I'm performing authorization using a custom HttpMessageHandler. However, my controller needs to know the username specified in the credentials.
Researching this, it appears that ApiController does not have a Controller.HttpContext property. And I see there are potential issues accessing HttpContext.Current. So while I am actually able to store the username in HttpContext.Current.Items in my HttpMessageHandler and then access that information from my controller, I'm not sure that will always be reliable.
I also saw recommendations to use the RequestContext.Principal property; however, I could not find the current request's username anywhere in this data.
How can my controller reliably get the username for the current request?
NOTE: I refer to the username but in this case the actual user is another piece of software calling the API. The "username" reflects the software that is making the call.
#Win: Well, that part is what I am developing. But currently basic
authentication seems appropriate, where the username identifies the
software contacting us and the password is a special key
Here is the sample code for BasicAuthenticationMessageHandler which uses message handler to support HTTP Basic Authentication.
You can read more at Page 121 of ASP.NET Web API 2: Building a REST Service from Start to Finish.
IBasicSecurityService
public interface IBasicSecurityService
{
bool SetPrincipal(string username, string password);
}
BasicSecurityService
public class BasicSecurityService : IBasicSecurityService
{
public bool SetPrincipal(string username, string password)
{
// Get user from database
var user = GetUser(username);
IPrincipal principal = null;
if (user == null || (principal = GetPrincipal(user)) == null)
{
// System could not validate user
return false;
}
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
return true;
}
public virtual IPrincipal GetPrincipal(User user)
{
var identity = new GenericIdentity(user.Username, Constants.SchemeTypes.Basic);
identity.AddClaim(new Claim(ClaimTypes.GivenName, user.Firstname));
identity.AddClaim(new Claim(ClaimTypes.Surname, user.Lastname));
// Get authroized roles and add them as Role Claim.
identity.AddClaim(new Claim(ClaimTypes.Role, "Manager"));
return new ClaimsPrincipal(identity);
}
}
BasicAuthenticationMessageHandler
public class BasicAuthenticationMessageHandler : DelegatingHandler
{
public const char AuthorizationHeaderSeparator = ':';
private const int UsernameIndex = 0;
private const int PasswordIndex = 1;
private const int ExpectedCredentialCount = 2;
private readonly IBasicSecurityService _basicSecurityService;
public BasicAuthenticationMessageHandler(IBasicSecurityService basicSecurityService)
{
_basicSecurityService = basicSecurityService;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// Already authenticated; passing on to next handler...
return await base.SendAsync(request, cancellationToken);
}
if (!CanHandleAuthentication(request))
{
// Not a basic auth request; passing on to next handler...
return await base.SendAsync(request, cancellationToken);
}
bool isAuthenticated;
try
{
isAuthenticated = Authenticate(request);
}
catch (Exception e)
{
// Failure in auth processing
return CreateUnauthorizedResponse();
}
if (isAuthenticated)
{
var response = await base.SendAsync(request, cancellationToken);
return response;
}
return CreateUnauthorizedResponse();
}
public bool CanHandleAuthentication(HttpRequestMessage request)
{
return (request.Headers != null
&& request.Headers.Authorization != null
&& request.Headers.Authorization.Scheme.ToLowerInvariant() == Constants.SchemeTypes.Basic);
}
public bool Authenticate(HttpRequestMessage request)
{
// Attempting to authenticate...
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
return false;
}
var credentialParts = GetCredentialParts(authHeader);
if (credentialParts.Length != ExpectedCredentialCount)
{
return false;
}
return _basicSecurityService.SetPrincipal(credentialParts[UsernameIndex], credentialParts[PasswordIndex]);
}
public string[] GetCredentialParts(AuthenticationHeaderValue authHeader)
{
var encodedCredentials = authHeader.Parameter;
var credentialBytes = Convert.FromBase64String(encodedCredentials);
var credentials = Encoding.ASCII.GetString(credentialBytes);
var credentialParts = credentials.Split(AuthorizationHeaderSeparator);
return credentialParts;
}
public HttpResponseMessage CreateUnauthorizedResponse()
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Constants.SchemeTypes.Basic));
return response;
}
}
Server
SignalR hub within MVC 5 WebApi 2,
Security: Bearer token
Client
C# class using HttpWebRequest to retrieve bearer token from WebApi controller /Token endpoint
I used the pattern described here and here to deliver the bearer token to my AuthorizeAttribute sub-class.
When the code within the AuthorizeHubConnection method executes the ticket delivered by the call to "secureDataFormat.Unprotect(token)" is always null. I have confirmed the token is identical on both ends of the communication.
Here is the override method:
public override bool AuthorizeHubConnection(AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, IRequest request)
{
var dataProtectionProvider = new DpapiDataProtectionProvider();
var secureDataFormat = new TicketDataFormat(dataProtectionProvider.Create());
var token = request.QueryString.Get("Bearer");
var ticket = secureDataFormat.Unprotect(token);
if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
{
// set the authenticated user principal into environment so that it can be used in the future
request.Environment["server.User"] = new ClaimsPrincipal(ticket.Identity);
return true;
}
return false;
}
When I run hub without the authorize attribute and set a breakpoint within the "OnConnected" override, the Context.User property is also null.
Any assistance would be greatly appreciated.
Rich
Finally figured this out, I was using the wrong library to decrypt the token. DpapiDataProtectionProvider is used in self-host scenarios, we are hosted in IIS. Here is the functioning code.
public override bool AuthorizeHubConnection(Microsoft.AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, IRequest request)
{
var token = request.QueryString.Get("Bearer");
var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(token);
if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
{
// set the authenticated user principal into environment so that it can be used in the future
request.Environment["server.User"] = new ClaimsPrincipal(ticket.Identity);
return true;
}
return false;
}
Here is my solution, WORK on Azure and local. AngularJS, Web API and SignalR
request.Environment["server.User"] this code doesn't work on Azure.
First i create my Custom Filter Class.
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class QueryStringBearerAuthorizeAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(Microsoft.AspNet.SignalR.Hubs.HubDescriptor hubDescriptor, IRequest request)
{
var _Authorization = request.QueryString.Get("Bearer");
if (!string.IsNullOrEmpty(_Authorization))
{
var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(_Authorization);
if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
{
request.Environment["server.User"] = new ClaimsPrincipal(ticket.Identity);
return true;
}
}
return false;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
var request=hubIncomingInvokerContext.Hub.Context.Request;
var _Authorization = request.QueryString.Get("Bearer");
if (!string.IsNullOrEmpty(_Authorization))
{
//var token = _Authorization.Replace("Bearer ", "");
var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(_Authorization);
if (ticket != null && ticket.Identity != null && ticket.Identity.IsAuthenticated)
{
Dictionary<string, object> _DCI = new Dictionary<string, object>();
_DCI.Add("server.User", new ClaimsPrincipal(ticket.Identity));
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(_DCI), connectionId);
return true;
}
}
return false;
}
}
Then in all my connection from SignalR i put
connection.qs = { Bearer:
localStorageService.get('authorizationData').token };
My Startup Class
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors = true
};
var authorizer = new QueryStringBearerAuthorizeAttribute();
var module = new AuthorizeModule(authorizer, authorizer);
GlobalHost.HubPipeline.AddModule(module);
map.RunSignalR(hubConfiguration);
});
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
ConfigureAuth(app);
}
It works perfect for me, i'm not sure if sending my token for quesry string instead from header is a security issue. Thats my solution using angularjs, asp.net web api, signal r for autenticate SignalR hubs with a beared token.
In your Hub you can Access to User variable in this way
public ClaimsPrincipal _User { get { return Context.Request.Environment["server.User"] as ClaimsPrincipal; } }
I have been experimenting with WP7 apps today and have hit a bit of a wall.
I like to have seperation between the UI and the main app code but Ive hit a wall.
I have succesfully implemented a webclient request and gotten a result, but because the call is async I dont know how to pass this backup to the UI level. I cannot seem to hack in a wait for response to complete or anything.
I must be doing something wrong.
(this is the xbox360Voice library that I have for download on my website: http://www.jamesstuddart.co.uk/Projects/ASP.Net/Xbox_Feeds/ which I am porting to WP7 as a test)
here is the backend code snippet:
internal const string BaseUrlFormat = "http://www.360voice.com/api/gamertag-profile.asp?tag={0}";
internal static string ResponseXml { get; set; }
internal static WebClient Client = new WebClient();
public static XboxGamer? GetGamer(string gamerTag)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null);
return SerializeResponse(response);
}
internal static XboxGamer? SerializeResponse(string response)
{
if (string.IsNullOrEmpty(response))
{
return null;
}
var tempGamer = new XboxGamer();
var gamer = (XboxGamer)SerializationMethods.Deserialize(tempGamer, response);
return gamer;
}
internal static string GetResponse(string url, string userName, string password)
{
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
Client.Credentials = new NetworkCredential(userName, password);
}
try
{
Client.DownloadStringCompleted += ClientDownloadStringCompleted;
Client.DownloadStringAsync(new Uri(url));
return ResponseXml;
}
catch (Exception ex)
{
return null;
}
}
internal static void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
ResponseXml = e.Result;
}
}
and this is the front end code:
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
var xboxGamer = xboxManager.GetGamer();
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
Now I understand I need to place something in ClientDownloadStringCompleted but I am unsure what.
The problem you have is that as soon as an asynchronous operation is introduced in to the code path the entire code path needs to become asynchronous.
Because GetResponse calls DownloadStringAsync it must become asynchronous, it can't return a string, it can only do that on a callback
Because GetGamer calls GetResponse which is now asynchronous it can't return a XboxGamer, it can only do that on a callback
Because GetGamerDetails calls GetGamer which is now asynchronous it can't continue with its code following the call, it can only do that after it has received a call back from GetGamer.
Because GetGamerDetails is now asynchronous anything call it must also acknowledge this behaviour.
.... this continues all the way up to the top of the chain where a user event will have occured.
Here is some air code that knocks some asynchronicity in to the code.
public static void GetGamer(string gamerTag, Action<XboxGamer?> completed)
{
var url = string.Format(BaseUrlFormat, gamerTag);
var response = GetResponse(url, null, null, (response) =>
{
completed(SerializeResponse(response));
});
}
internal static string GetResponse(string url, string userName, string password, Action<string> completed)
{
WebClient client = new WebClient();
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
client.Credentials = new NetworkCredential(userName, password);
}
try
{
client.DownloadStringCompleted += (s, args) =>
{
// Messy error handling needed here, out of scope
completed(args.Result);
};
client.DownloadStringAsync(new Uri(url));
}
catch
{
completed(null);
}
}
public void GetGamerDetails()
{
var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
xboxManager.GetGamer( (xboxGamer) =>
{
// Need to move to the main UI thread.
Dispatcher.BeginInvoke(new Action<XboxGamer?>(DisplayGamerDetails), xboxGamer);
});
}
void DisplayGamerDetails(XboxGamer? xboxGamer)
{
if (xboxGamer.HasValue)
{
var profile = xboxGamer.Value.Profile[0];
imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
txtUserName.Text = profile.GamerTag;
txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
txtZone.Text = profile.PlayerZone;
}
else
{
txtUserName.Text = "Failed to load data";
}
}
As you can see async programming can get realy messy.
You generally have 2 options. Either you expose your backend code as an async API as well, or you need to wait for the call to complete in GetResponse.
Doing it the async way would mean starting the process one place, then return, and have the UI update when data is available. This is generally the preferred way, since calling a blocking method on the UI thread will make your app seem unresponsive as long as the method is running.
I think the "Silverlight Way" would be to use databinding. Your XboxGamer object should implement the INotifyPropertyChanged interface. When you call GetGamer() it returns immediately with an "empty" XboxGamer object (maybe with GamerTag=="Loading..." or something). In your ClientDownloadStringCompleted handler you should deserialize the returned XML and then fire the INotifyPropertyChanged.PropertyChanged event.
If you look at the "Windows Phone Databound Application" project template in the SDK, the ItemViewModel class is implemented this way.
Here is how you can expose asynchronous features to any type on WP7.