How to access username from controller - asp.net

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;
}
}

Related

Challenge() always redirects to Login Page

I am trying to authorize my Action method. Everything works fine but my Challenge() method always redirect to Login Page even if I've already logged in. I try to return Forbid() method and it redirects to Access Denied page as it should. What might be the problem with Challenge() method ?
public async Task<IActionResult> Edit(int id)
{
var project = await _context.Project.Include(p => p.OrganizationsLink).FirstOrDefaultAsync(p => p.Id == id);
if (project == null)
return NotFound();
//AUTHORIZATION
var allowed = await _authz.AuthorizeAsync(User, null, new ProjectEditRequirement(project));
if (!allowed.Succeeded)
return Challenge();
return View(project);
}
For Challenge, it is controlled by IAuthenticationService. AuthenticationService will call AuthenticateAsync to invoke the handler.AuthenticateAsync().
Not sure whether you implement custom IAuthenticationHandler, I will go deep the CookieAuthenticationHandler.
The complete workflow for CookieAuthenticaiton is below:
return Challenge() , for Challenge
public virtual ChallengeResult Challenge()
=> new ChallengeResult();
ChanllengeResult will call ExecuteResultAsync
public override async Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<ChallengeResult>();
logger.ChallengeResultExecuting(AuthenticationSchemes);
if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
{
foreach (var scheme in AuthenticationSchemes)
{
await context.HttpContext.ChallengeAsync(scheme, Properties);
}
}
else
{
await context.HttpContext.ChallengeAsync(Properties);
}
}
context.HttpContext.ChallengeAsync will invoke ChallengeAsync
public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
context.RequestServices.GetRequiredService<IAuthenticationService>().ChallengeAsync(context, scheme, properties);
For CookieAuthenticationHandler
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var redirectUri = properties.RedirectUri;
if (string.IsNullOrEmpty(redirectUri))
{
redirectUri = OriginalPathBase + Request.Path + Request.QueryString;
}
var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(loginUri));
await Events.RedirectToLogin(redirectContext);
}
As you could find, the HandleChallengeAsync redirect the action.
In my option, you could try return Forbid(), otherwise, you will need to override the HandleChallengeAsync.

Webapi 2.0 how to implement refresh JWT token when access token expired

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/

how to retrieve facebook user info ASP.NET WEB API 2

I want to register a user, through an external provider (like facebook), in order to get the information I need, I configure FacebookProvider as follows
var options = new FacebookAuthenticationOptions {
AppId = "***",
AppSecret = "***",
Scope = { "email" },
Provider = new FacebookAuthenticationProvider {
OnAuthenticated = (context) => {
foreach (var x in context.User)
{
var claimType = string.Format("urn:facebook:{0}", x.Key);
string claimValue = x.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));
}
return Task.FromResult(0);
}
}
};
options.Fields.Add("id");
options.Fields.Add("name");
options.Fields.Add("email");
options.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalBearer;
app.UseFacebookAuthentication(options);
in the OnAuthenticated while debugging I see all the requested fields but when I call RegisterExternal from postman as follow pic
RegisterExternal call postman
GetExternalLoginInfoAsync returns null
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
so how to retrieve query fields such as email? I think all the necessary information is stored in the cookies, but how do I transfer them to the server and extract Identity instance?
all nuget packages have been updated to the latests versions
p.s. I plan to work with the API from the iOS app
I found solution.
Changed ExternalLoginData class as follows
private class ExternalLoginData
{
...
// here added new field
public IList<Claim> Claims { get; private set; }
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
...
return new ExternalLoginData
{
...
// here added claims setting
Claims = identity.Claims.ToList()
};
}
}
Changed ExternalLogin callback as follows
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
...
if (hasRegistered)
{
...
}
else
{
// here replaced getting claims by Claims field
IEnumerable<Claim> claims = externalLogin.Claims;
//IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
}
As a result we receive a bearer token. Extracting Identity from it we receive earlier saved claims.

How to implement basic authentication in web API 2 (ASP.Net Core)?

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.

Unable to Identify User Context in SignalR hub decorated with "Authorize" attribute

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; } }

Resources