Custom WebAPI Authentication - How to authenticate HttpContext.Current.Request.LogonUserIdentity.Name - c#

I have an webapi that will live on a corporate network and will only have windows authenticated users.
I am trying to authenticate HttpContext.Current.Request.LogonUserIdentity.Name directly because HttpContext.Current.Request.LogonUserIdentity.IsAuthenticated returns false.
I am doing it this way to avoid the user login popup for non-admin users.
using System;
using System.Diagnostics;
using System.Web.Http;
using System.Web;
using System.Web.Http.Controllers;
namespace myAPI.Helpers
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeCustomAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
// HttpContext.Current.User.Identity.Name is always empty at this point
// and must authenticate first with HandleUnauthorizedRequest(actionContext)
// but that pops up an annoying login screen,
// HttpContext.Current.Request.LogonUserIdentity.Name has the value I need
// but it is not authenticated which raises some security concerns
// Check against a list of admins
if (HttpContext.Current.Request.LogonUserIdentity.IsAuthenticated && Authentication.IsAdmin( HttpContext.Current.Request.LogonUserIdentity.Name ))
{
Debug.WriteLine("USER IS AUTHORIZED");
}
else
{
Debug.WriteLine("USER IS DENIED");
//HandleUnauthorizedRequest(actionContext); // This will popup a login unless it is overridden
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK); // return a blank response instead
}
}
}
}

This was my simplest solution:
Only check authentication of known admins
Redirect admins that are not authenticated
Non-admins won't get a login popup
using System;
using System.Diagnostics;
using System.Web.Http;
using System.Web;
using System.Web.Http.Controllers;
namespace myAPI.Helpers
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeCustomAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
// Check against a list of admins
if (Authentication.IsAdmin(HttpContext.Current.User.Identity.Name) || Authentication.IsAdmin( HttpContext.Current.Request.LogonUserIdentity.Name ))
{
if(HttpContext.Current.User.Identity.IsAuthenticated || HttpContext.Current.Request.LogonUserIdentity.IsAuthenticated )
{
Debug.WriteLine("USER IS AUTHORIZED");
} else
{
Debug.WriteLine("USER IS AN ADMIN BUT IS UNAUTHENTICATED");
HandleUnauthorizedRequest(actionContext); // redirect to get authenticated
}
}
else
{
Debug.WriteLine("USER IS NOT AN ADMIN AND IS DENIED");
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK); // return a blank response
}
}
}
}

Related

Dynamically apply values to the Roles property for the AuthorizedAttribute class

We have an application that have three levels of roles. Depending on which role a user is in determines what they have access to. The users are in activedomain groups. The client has asked that test groups be created for test and the original groups are to be used in production. We're using web.config transforms to distinguish the groups in each environment.
Previous to this the AuthorizedAttribute class was set up for a save method as follows:
[AuthorizeUsers(Roles = "Group A, Administration")]
public SomeInformation Post([FromBody]SomeInformation infodata)
{
return _manager.SaveInfo(infodata);
}
The Group A and Administration are production. In test there is now Group A - Test and Administration - Test.
Is there a way to pull from the web.config file and assign it to the Roles property?
We tried the following:
private string Env = Settings.Default.GroupA + ", " + Settings.Default.Administration
[AuthorizeUsers(Roles = Env)]
But it gacked. The AuthorizeUsers class inherits from AuthorizeAttribute. Any ideas?
You need to make a custom authorize attribute that is read by a custom authorization filter. If you register the filter globally, it will run at the right time and recognize when your controllers and action methods are decorated with the custom authorize attribute.
ConfigAuthorizeAttribute
using System;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class ConfigAuthorizeAttribute : Attribute
{
public string RolesConfigurationKey { get; set; }
public string UsersConfigurationKey { get; set; }
}
ConfigAuthorizationFilter
using System;
using System.ComponentModel;
using System.Configuration;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
public class ConfigAuthorizationFilter : AuthorizeAttribute
{
// Hide users and roles, since we aren't using them here.
[Obsolete("Not applicable in this class.", true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
new public string Roles { get; set; }
[Obsolete("Not applicable in this class.", true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
new public string Users { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Pass the current action descriptor to the AuthorizeCore
// method on the same thread by using HttpContext.Items
filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
if (actionDescriptor != null)
{
var authorizeAttribute = this.GetConfigAuthorizeAttribute(actionDescriptor);
// If the authorization attribute exists
if (authorizeAttribute != null)
{
// Ensure the user is logged in
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
// Lookup the Web.Config settings
string users = ConfigurationManager.AppSettings[authorizeAttribute.UsersConfigurationKey];
string roles = ConfigurationManager.AppSettings[authorizeAttribute.RolesConfigurationKey];
string[] usersSplit = SplitString(users);
string[] rolesSplit = SplitString(roles);
// Run the authorization based on the configuration values
if (usersSplit.Length > 0 && !usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (rolesSplit.Length > 0 && !rolesSplit.Any(user.IsInRole))
{
return false;
}
return true;
}
}
return true;
}
private ConfigAuthorizeAttribute GetConfigAuthorizeAttribute(ActionDescriptor actionDescriptor)
{
ConfigAuthorizeAttribute result = null;
// Check if the attribute exists on the action method
result = (ConfigAuthorizeAttribute)actionDescriptor
.GetCustomAttributes(attributeType: typeof(ConfigAuthorizeAttribute), inherit: true)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (ConfigAuthorizeAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(attributeType: typeof(ConfigAuthorizeAttribute), inherit: true)
.SingleOrDefault();
return result;
}
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Usage
First, register your global filter.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ConfigAuthorizationFilter());
filters.Add(new HandleErrorAttribute());
}
}
Then, decorate your controllers and actions with the ConfigAuthorizeAttribute, specifying where to lookup the users and roles in the configuration file.
[ConfigAuthorize(RolesConfigurationKey = "authorization:groups:home-about")]
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
Then, add the configuration file settings to appSettings in web.config.
<appSettings>
<add key="authorization:groups:home-about" value="Group A, Administration"/>
</appSettings>

asp.net MVC authentication with Shibboleth

Shibboleth is a SSO Authentication that is added to IIS as a "plugin".
After a user has done a Login there are Headers showing the Shibboleth Session:
ShibSessionID
ShibIdentityProvider
eppn
affiliation
entitlement
unscopedaffiliation
...more
So i can extract username and roles from the Headers.
so far so fine.
Question:
How can I implement a handler that does read the headers and set the status that a user is authorized?
Idea is to use the
[Authorize]
Attribute and the Method
Roles.IsUserInRole.
All from the Headers, no Database, no User Management.
Update
Implementation According to the Answer from #Pharylon
In this Update there is nothing new, just a help for the copy&past friends.
Of course you have to adjust the properties and Header fieldnames according to your Shibboleth Setup.
File: ShibbolethPrincipal.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal; //GenericPrincipal
namespace Shibboleth
{
public class ShibbolethPrincipal : GenericPrincipal
{
public string username
{
get { return this.Identity.Name.Replace("#ksz.ch", ""); }
}
public string firstname
{
get { return HttpContext.Current.Request.Headers["givenName"]; }
}
public string lastname
{
get { return HttpContext.Current.Request.Headers["surname"]; }
}
public string phone
{
get { return HttpContext.Current.Request.Headers["telephoneNumber"]; }
}
public string mobile
{
get { return HttpContext.Current.Request.Headers["mobile"]; }
}
public string entitlement
{
get { return HttpContext.Current.Request.Headers["eduzgEntitlement"]; }
}
public string homeOrganization
{
get { return HttpContext.Current.Request.Headers["homeOrganization"]; }
}
public DateTime birthday
{
get
{
DateTime dtHappy = DateTime.MinValue;
try
{
dtHappy = DateTime.Parse(HttpContext.Current.Request.Headers["dateOfBirth"]);
}
finally
{
}
return dtHappy;
}
set {}
}
public ShibbolethPrincipal()
: base(new GenericIdentity(GetUserIdentityFromHeaders()), GetRolesFromHeader())
{
}
public static string GetUserIdentityFromHeaders()
{
//return HttpContext.Current.Request.Headers["eppn"];
return HttpContext.Current.Request.Headers["principalName"];
}
public static string[] GetRolesFromHeader()
{
string[] roles = null;
//string rolesheader = HttpContext.Current.Request.Headers["affiliation"];
string rolesheader = HttpContext.Current.Request.Headers["eduzgEntitlement"];
if (rolesheader != null)
{
roles = rolesheader.Split(';');
}
return roles;
}
}
}
File: ShibbolethController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Shibboleth
{
public class ShibbolethController : Controller
{
protected new ShibbolethPrincipal User
{
get
{
return (base.User as ShibbolethPrincipal) ?? null; //CustomPrincipal.GetUnauthorizedPrincipal();
}
}
}
}
File: Global.asax
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var ctx = HttpContext.Current;
var principal = new ShibbolethPrincipal();
HttpContext.Current.User = principal;
}
Using examples:
namespace itservices.Controllers
{
[Authorize] //examples : [Authorize(Roles="Administrators")], [Authorize(Users="Alice,Bob")]
public class PasswordMailController : ShibbolethController
{
if(User.IsInRole("staff"))
{
You'll want to create a method in Global.asax.cs that has the following signature
protected void Application_PostAuthenticateRequest()
{
//Your code here.
}
This will be called automatically before almost anything else is done (MVC will call this method if it exists, you don't have to "turn it on" anywhere), and this is where you need to set the Principal. For instance, let's assume you have a header called RolesHeader that has a comma separated value of roles and another header called UserId that has (duh) the user ID.
Your code, without any error handling, might look something like:
protected void Application_PostAuthenticateRequest()
{
var rolesheader = Context.Request.Headers["RolesHeader"];
var userId = Context.Request.Headers["UserId"];
var roles = rolesheader.Split(',');
var principal = new GenericPrincipal(new GenericIdentity(userId), roles);
Context.User = principal;
}
It's the Principal/Identity that the [Authorize] attribute uses, so setting it here at the beginning of the request lifecycle means the [Authorize] attribute will work correctly.
The rest of this is optional, but I recommend it:
I like to create my own custom classes that implement IPrincipal and IIdentity instead of using the GenericPrincipal and GenericIdentity, so I can stuff more user information in it. My custom Principal and Identity objects then have much more rich information, such as branch numbers or email addresses or whatever.
Then, I create a Controller called BaseController that has the following
protected new CustomPrincipal User
{
get
{
return (base.User as CustomPrincipal) ?? CustomPrincipal.GetUnauthorizedPrincipal();
}
}
This allows me to access all my rich, custom Principal data instead of just what's defined in IPrincipal. All of my real controllers then inherit from BaseController instead of directly from Controller.
Obviously, when using a custom Principal like this, in the Application_PostAuthenticateRequest() method, you'd set the Context.User to be your CustomPrincipal instead of a GenericPrincipal.

ASP.NET WEB API Forms Authentication Error

I am trying to create a web api with forms based authentication. I want to login from a client and retrieve data from there. When I log in, user gets authenticated and can retrieve data by giving http request direct into adressbar like localhost:1393/api/Game. But when i try to get it from client I am getting a 401 (Unauthorized error). I have enabled CORS in server side. This is the controller to handle data
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Security;
using Cheeky_backend.Models;
using System.Web.Http.WebHost;
namespace Cheeky_backend.Controllers
{
public class Demo
{
public List<Teams> team { get; set; }
public List<Hole> hole { get; set; }
}
[Authorize]
public class GameController : ApiController
{
private Cheeky_backendContext db = new Cheeky_backendContext();
// GET api/Game
public IEnumerable<Hole> GetHoles()
{
return db.Holes.AsEnumerable();
}
}
}
This is the authenticating controler
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Security;
using System.Web.Http;
using Cheeky_backend.Models;
namespace Cheeky_backend.Controllers
{
public class UserController : ApiController
{
private Cheeky_backendContext db = new Cheeky_backendContext();
// GET api/Default1
// GET api/Default1/5
// PUT api/Default1/5
// POST api/Default1
public HttpResponseMessage CreateUser(User user)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, user);
// response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = user.ID }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
// DELETE api/Default1/5
public HttpResponseMessage Login(User user)
{
var userfound = from user2 in db.Users
where user.username == user2.username && user.password == user2.password
select user2;
if( userfound.Any())
{
FormsAuthentication.SetAuthCookie(user.username, true);
return Request.CreateResponse(HttpStatusCode.OK,user);
}
return Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
Source
In your Authentication Handler
Don't set the Principal on the Thread.CurrentPrinicipal any more.
Use the Principal on the HttpRequestContext.
Take a look at here

ASP.Net MVC 3 First login / complete profile

I am using the FormsAuthenticationService along with AccountMembershipService to handle membership and log in. I need a way to force a user to complete their profile when they log into the site, or go to a area that requires [Authorize()], before being able to continue on the site.
I was thinking about using a GlobalFilter on the AuthorizationContext but not sure if this would work as required.
Any ideas?
Following a bit of digging about I managed to find a way to achieve this.
First created a global filter
using System.Web.Mvc;
using Microsoft.Practices.Unity;
public sealed class LogOnAuthorize : AuthorizeAttribute
{
[Dependency]
public Service.IDaoService dao { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
string SessionKey = "ProfileCompleted";
bool Authorization = filterContext.ActionDescriptor.IsDefined(typeof(AuthorizeAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AuthorizeAttribute), true);
bool ContainsIgnore = filterContext.ActionDescriptor.IsDefined(typeof(IgnoreCompleteProfileAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(IgnoreCompleteProfileAttribute), true);
if ((Authorization) && (!ContainsIgnore))
{
var ctx = System.Web.HttpContext.Current;
if (ctx != null)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (ctx.Session[SessionKey] == null)
{
Models.UserDetail user = dao.UserDao.GetByEmail(filterContext.HttpContext.User.Identity.Name);
if (user != null)
ctx.Session[SessionKey] = user.CompletedAccount;
}
bool ForceRedirect = ((ctx.Session[SessionKey] == null) || ((bool)ctx.Session[SessionKey] == false));
string ReturnUrl = string.Empty;
if ((filterContext.HttpContext.Request != null) && (!string.IsNullOrEmpty(filterContext.HttpContext.Request.RawUrl)))
ReturnUrl = filterContext.HttpContext.Request.RawUrl.ToString();
if (ForceRedirect)
filterContext.Result = new RedirectToRouteResult("CompleteAccount", new System.Web.Routing.RouteValueDictionary { {"ReturnUrl" , ReturnUrl} });
}
else
base.OnAuthorization(filterContext);
}
else
base.OnAuthorization(filterContext);
}
}
}
Then registed it up in the global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LogOnAuthorize());
}
For any actions that I needed to be authorised but not perform the check on I used
[Authorize()]
[IgnoreCompleteProfileAttribute()]
public ActionResult LogOff()
{
// Code Here
}
Which used this class
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class IgnoreCompleteProfileAttribute : Attribute { }
Which all works well for what I needed, hope this helps someone.
I think you're on the right path with the global filter. You'd simply implement a check in the attribute that if the current user is authenticated and certain profile information is missing redirect them to the edit profile page. Depending on how you implement the redirect you could also inject some sort of feedback message to be displayed on the edit profile page explaining why they were redirected.

Authentication Webservice in Silverlight 4

I'm newbie to webservices..
I have a Silverlight 4 application, with a login page created be me.
I have created a webservice:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Security;
namespace MagNET.Web.WebServices
{
/// <summary>
/// Summary description for AuthenticationService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class AuthenticationService : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
public bool AuthenticateUser(string username, string password)
{
//bool valid = Membership.ValidateUser(username, password);
bool valid = true;
if (valid)
{
FormsAuthentication.SetAuthCookie(username, false);
Session["username"] = username;
}
else
{
Session["username"] = null;
}
return valid;
}
[WebMethod(EnableSession = true)]
public string HelloWorld()
{
if (!IsLoggedIn())
{
throw new Exception("User is not logged in!!!");
}
else
{
return "Hello World!";
}
}
private bool IsLoggedIn()
{
if (Session["username"] != null)
return true;
else
return false;
}
}
}
I use this code to find out if user is logged in:
MagNET.AuthenticationService.AuthenticationServiceSoapClient svc =
new MagNET.AuthenticationService.AuthenticationServiceSoapClient();
svc.HelloWorldCompleted += new EventHandler<AuthenticationService.HelloWorldCompletedEventArgs>(svc_HelloWorldCompleted);
svc.HelloWorldAsync();
void svc_HelloWorldCompleted(object sender, AuthenticationService.HelloWorldCompletedEventArgs e)
{
MessageBox.Show("User logged in");
}
I don't know how to invoke the AuthenticateUser method of the web service (I don't know how to effectively log in a user when login button from my login page is pressed).
Just use AuthenticateUser method instead HelloWorld method:
MagNET.AuthenticationService.AuthenticationServiceSoapClient svc =
new MagNET.AuthenticationService.AuthenticationServiceSoapClient();
svc.AuthenticateUserCompleted += new EventHandler<AuthenticationService.AuthenticateUserCompletedEventArgs>(svc_AuthenticateUserCompleted);
string username = txtBoxUsername.Text;
string password = txtBoxPassword.Password;
svc.AuthenticateUserAsync(username, password);

Categories