Using a custom MembershipProvider with Web API & MVC - forms-authentication

I have two controllers;
AccountController : Controller //For accounts <-- MVC
ItemController : ApiController //For Items <-- WEB API
The accounts controller is for all intents and purposes a pretty standard implementation of the Accounts code, the only major difference is that it's using a Custom MembershipProvider. On the accounts controller I also have one other Action:
[Authorize]
public ActionResult Bleh(){ return View(); }
Which if I attempt to get to:
http://localhost/Account/Bleh
Redirects me (as expected) to the Login page, which after logging in, returns me back to the Bleh page. All good. The problem is on the ItemController, I have an Action which too has an Authorize attribute on it:
[Authorize]
public HttpResponseMessage PostItem(Item item) { /**/ }
Going to this before logging in returns a 401 - Unauthorized - which again is as expected, but after logging in, it still returns a 401. I can't see why this is the case.
Am I missing any configuration elements? Routing? N.E. Other?
I was under the impression that Web Api would pick up the Forms Authentication in the same way as MVC, and I know the authentication is working as the MVC one is working.

Make sure that in the web.config hosting your API controller you have defined the authentication scheme:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="2400" />
</authentication>
or if you are using some other kind of custom authentication scheme that you are tracking users differently you might need to write a DelegatingHandler. Here's an example I wrote for Basic Authentication: https://stackoverflow.com/a/11536349/29407

I have rebuilt the application from scratch, and the forms code is working as expected now. I cannot for the life of me see any difference between my original code and the new code. I suspect maybe something in a config file somewhere, but after a pretty substantial amount of time it eludes me.

You can override OnRedirectToLogin Identity event and return response accordingly based on the fact that it is an API call (based on '/api/' in url)

Related

Thinktecture.IdentityModel with SimpleMembership

I want to use ASP.NET SimpleMembership to authenticate users that consume my WebAPI. Thinktecture has a wonderful authentication library called Thinktecture.IdentityModel (http://thinktecture.github.com/Thinktecture.IdentityModel.45/) with an example that ties Forms Auth with Basic Auth (source). However, the example uses Membership.ValidateUser() which doesn't work without a ASP.NET Membership provider, which isn't supported by SimpleMembership (source) (edit: this isn't entirely true, see Mark's answer below).
Edit:
Here's what I did:
1) Create a new MVC Internet Application
2) Install Thinktecture.IdentityModel via NuGet
3) Create a model and an api controller via scaffolding:
public class Goober
{
public int GooberId { get; set; }
public string GooberWords { get; set; }
}
4) Ran the project, created a new user and created a new Goober using Fiddler
5) Added [Authorize] to GetGoober(int id)
6) In WebApiConfig.cs added:
var authConfig = new AuthenticationConfiguration();
authConfig.AddBasicAuthentication((userName, password) =>
Membership.ValidateUser(userName, password));
config.MessageHandlers.Add(new AuthenticationHandler(authConfig));
When I run the project and hit api/goober/1 with Fiddler I get a 401 www-Authenticate: unspecified. But if I log in first using the AccountController then use Fiddler I get a 200 and everything is peachy.
Edit
Okay, I think the problem isn't related to my initial question. I suspect it's related to the initialization of SimpleMembership in the template. When I open the project and run debug then hit the api with Fiddler I can't get past Auth. But when I simply click the "register" link on the web frontend I get past Auth. I'm guessing it's because the InitializeSimpleMembershipAttribute is called at the AccountController so doesn't initialize until the controller is called?
I've tried using WebSecurity.Login() in the place of Membership.ValidateUser() but that doesn't work.
I'm at a loss on how to actually implement this. Does anyone have any advice? Or maybe I'm attempting to tackle this problem from the wrong angle?
You are correct that ASP.NET Membership provider is not compatible with the SimpleMembershipProvider however SimpleMembershipProvider does support ValidateUser, see here. Assuming SimpleMembership is correctly configured and initalised you should still be able to call Membership.ValidateUser().
If you have already tried Membership.ValidateUser() and got an error please let me know and we can try resolve it.
Update
So having followed your reproduction steps I have managed to pin-point an error. Having brought the Thinktecture AuthenticationHandler handler inline and run in debug. 30 seconds after a request to the api controller a database connection error is being raised. This is failing asynchronously and silently.
After some fiddling around I believe it is the DefaultConnection connection string which is at fault.
Like me your default connection probably contains a file name like this:
AttachDBFilename=|DataDirectory|\aspnet-MvcApplication3-20121215104323.mdf"
When ValidateUser is called inside the delegate registered at app start up (for validating the credentials) it appears to be failing to resolve |DataDirectory| I found that by updating this path to the full name my connection problems went away.
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MvcApplication3-20121215104323;Integrated Security=SSPI;AttachDBFilename=C:\mydatabase\file\path\example\aspnet-MvcApplication-20121215104323.mdf" providerName="System.Data.SqlClient" />
</connectionStrings>
I then found this post here, it indicates that the AppDomain has not had it's data directory set correctly at this point.
So once the config set up and the connection string altered with a proper file path and a user name "test" and a password "testing" this request through fiddler got a 200:
GET http://localhost/api/goober/1
User-Agent: Fiddler
Host: localhost
Authorization: Basic dGVzdDp0ZXN0aW5n
As an aside
I found that to get the forms authorisation token to also allow access to the api controllers I had to add this. Otherwise the Thinkteckture code sets the principle back to anonymous:
Add this
authConfig.InheritHostClientIdentity = true;
To counteract this (line 52):
if (_authN.Configuration.InheritHostClientIdentity == false)
{
//Tracing.Information(Area.HttpAuthentication, "Setting anonymous principal");
SetPrincipal(Principal.Anonymous);
}

User Authentication in ASP.NET Web API

I need to develop an iPhone client that consumes JSON data from somewhere. I chose Web API from MS because it seemed easy enough but when it comes to authenticating users, things get quite frustrating.
I am amazed how I've not been able to find a clear example of how to authenticate a user right from the login screen down to using the Authorize attribute over my ApiController methods after several hours of Googling.
This is not a question but a request for an example of how to do this exactly. I have looked at the following pages:
Making your ASP.NET Web API's Secure
Basic Authentication With ASP.NET Web API
Even though these explain how to handle unauthorized requests, these do not demonstrate clearly something like a LoginController or something like that to ask for user credentials and validate them.
Anyone willing to write a nice simple example or point me in the right direction, please?
I am amazed how I've not been able to find a clear example of how to authenticate a user right from the login screen down to using the Authorize attribute over my ApiController methods after several hours of Googling.
That's because you are getting confused about these two concepts:
Authentication is the mechanism whereby systems may securely identify their users. Authentication systems provide an answers to the questions:
Who is the user?
Is the user really who he/she represents himself to be?
Authorization is the mechanism by which a system determines what level of access a particular authenticated user should have to secured resources controlled by the system. For example, a database management system might be designed so as to provide certain specified individuals with the ability to retrieve information from a database but not the ability to change data stored in the datbase, while giving other individuals the ability to change data. Authorization systems provide answers to the questions:
Is user X authorized to access resource R?
Is user X authorized to perform operation P?
Is user X authorized to perform operation P on resource R?
The Authorize attribute in MVC is used to apply access rules, for example:
[System.Web.Http.Authorize(Roles = "Admin, Super User")]
public ActionResult AdministratorsOnly()
{
return View();
}
The above rule will allow only users in the Admin and Super User roles to access the method
These rules can also be set in the web.config file, using the location element. Example:
<location path="Home/AdministratorsOnly">
<system.web>
<authorization>
<allow roles="Administrators"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
However, before those authorization rules are executed, you have to be authenticated to the current web site.
Even though these explain how to handle unauthorized requests, these do not demonstrate clearly something like a LoginController or something like that to ask for user credentials and validate them.
From here, we could split the problem in two:
Authenticate users when consuming the Web API services within the same Web application
This would be the simplest approach, because you would rely on the Authentication in ASP.Net
This is a simple example:
Web.config
<authentication mode="Forms">
<forms
protection="All"
slidingExpiration="true"
loginUrl="account/login"
cookieless="UseCookies"
enableCrossAppRedirects="false"
name="cookieName"
/>
</authentication>
Users will be redirected to the account/login route, there you would render custom controls to ask for user credentials and then you would set the authentication cookie using:
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
Cross - platform authentication
This case would be when you are only exposing Web API services within the Web application therefore, you would have another client consuming the services, the client could be another Web application or any .Net application (Win Forms, WPF, console, Windows service, etc)
For example assume that you will be consuming the Web API service from another web application on the same network domain (within an intranet), in this case you could rely on the Windows authentication provided by ASP.Net.
<authentication mode="Windows" />
If your services are exposed on the Internet, then you would need to pass the authenticated tokens to each Web API service.
For more info, take a loot to the following articles:
http://stevescodingblog.co.uk/basic-authentication-with-asp-net-webapi/
http://codebetter.com/johnvpetersen/2012/04/02/making-your-asp-net-web-apis-secure/
If you want to authenticate against a user name and password and without an authorization cookie, the MVC4 Authorize attribute won't work out of the box. However, you can add the following helper method to your controller to accept basic authentication headers. Call it from the beginning of your controller's methods.
void EnsureAuthenticated(string role)
{
string[] parts = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(Request.Headers.Authorization.Parameter)).Split(':');
if (parts.Length != 2 || !Membership.ValidateUser(parts[0], parts[1]))
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "No account with that username and password"));
if (role != null && !Roles.IsUserInRole(parts[0], role))
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "An administrator account is required"));
}
From the client side, this helper creates a HttpClient with the authentication header in place:
static HttpClient CreateBasicAuthenticationHttpClient(string userName, string password)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(userName + ':' + password)));
return client;
}
I am working on a MVC5/Web API project and needed to be able to get authorization for the Web Api methods. When my index view is first loaded I make a call to the 'token' Web API method which I believe is created automatically.
The client side code (CoffeeScript) to get the token is:
getAuthenticationToken = (username, password) ->
dataToSend = "username=" + username + "&password=" + password
dataToSend += "&grant_type=password"
$.post("/token", dataToSend).success saveAccessToken
If successful the following is called, which saves the authentication token locally:
saveAccessToken = (response) ->
window.authenticationToken = response.access_token
Then if I need to make an Ajax call to a Web API method that has the [Authorize] tag I simply add the following header to my Ajax call:
{ "Authorization": "Bearer " + window.authenticationToken }

Windows Authentication - Getting current user name

In my MVC application I want to render a table in a cshtml file, if the current log in user is some x person. I am using windows authentication and I have made the following changes in web.config file.
<authentication mode="Windows">
</authentication>
And in my controller when I am trying to access the current user name I am not getting any user name. I am trying the following:
ViewBag.LogInUserName = Request.RequestContext.HttpContext.User.Identity.Name;
This above line was working before. But I don't know whats wrong now. Also I have hosted my application on IIS now.
Take a look at the web project's properties, in particular:
Anonymous Authentication - Set to "Disabled"
Windows Authentication - Set to "Enabled"
By default these are set to the opposite of what you're probably looking for.
(Image sourced from MSDN)
You need to put the [Authorize] attribute on your controller.
You can use User.Identity.Name in your controllers.
[Authorize]
public class YourController : Controller
{
public ActionResult SomeAction()
{
var userName = User.Identity.Name;
}
}
A little bit late, but this may serve others in the future.
I had the same problem once after deploying my site to a new IIS server, and the anonymous authentication was enabled, so make sure that anonymous authentication is disabled and it should work.

ASP.NET MVC and ASP.NET membership provider - Handle authentication globally

I'm building a small app with ASP.NET MVC and I'm using the ASP.NET membership provider for handling users. Hooked this up to the login page of the basic MVC template.
What is the best practice for checking a valid authentication globaly? I basically want to redirect to the front page or the login page if the user's not authenticated on all my pages.
-anders
The way we did it, back in the days of MVC Preview 4 or so, was to create a new "BaseController" class, which every other controller then inherits from. This BaseController class uses the Authorize attribute
[Authorize]
public class BaseController : Controller
{
...
}
The rest of our controllers then inherited from this one
public class HomeController : BaseController
{
...
}
Haven't had to work with MVC for a few months now, so I can't say if this is still applicable, so proceed with caution...
You should just annotate any action you want to authenticate with [Authorize], and optionally with some required roles:
[Authorize()]
public ActionResult Index() {
...
return View();
}
This includes your home page action, if you wish. Unauthorized attempts will be always redirected to the login page.
this may be slightly over complicated, but another approach could be to put a custom HTTP Module in the pipeline to redirect the request if the user isn't authenticated.

ASP.Net MVC Authorisation action filter

I'm trying to understand how error handling works when using the Authorize [Authorize] Action Filter in MVC Preview 4.
I have an action that looks like this:
[Authorize(Roles = "DOMAIN\\NOTAUTHORISED_ROLE" )]
[HandleError]
public ActionResult NeedAuthorisation()
{
throw new NotImplementedException();
}
When I visit the url: http://localhost:2197/testAuthorisation/NeedAuthorisation, I get a blank page in my browser. In Firebug I can see that a request was made and a response-status of 401 - Unauthorised has been returned. But I'm not being redirected or having a customError returned. Everything works as expected when using a role that I'm authorized for.
This is using Windows authentication. I'm in the middle of writing some code to try out Forms authentication to see if I get the same issue.
I have <customerrors mode="On"/> set and have created error pages, both in the testAuthorisation folder and the Shared folder.
I eventually found this MVC tutorial which solved my problem:
Exactly what happens when you attempt to invoke a controller action
without being the right permissions depends on the type of
authentication enabled. By default, when using the ASP.NET Development
Server, you simply get a blank page. The page is served with a 401 Not
Authorized HTTP Response Status.
If you've got CustomErrors set to Off or RemoteOnly then you won't get re-directed to the page specified by HandleError (default is Error.aspx). Set it to "On" and then see what happens. Any custom error pages you specify explicitly will take precedence, however, so you need to remove these, and have just:
<customErrors mode="On" />
You need an error view in the corresponding view folder, i.e. you need the file Views/TestAuthorization/Error.aspx in order to have anything show up.
You can also customize this behaviour by what view that you want to use and to what exception you want it to be triggered with.
[HandleError(ExceptionType = typeof(SqlException), View = "DatabaseError")]]
[HandleError(ExceptionType = typeof(NullReferenceException), View = "LameErrorHandling")]]

Resources