User Authentication in ASP.NET Web API - asp.net

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 }

Related

Sccess some folder on file share based on user authenticated

I have an asp.net web application with forms authentication and users (credentials) are checked against active directory, username is actually samAccountName attribute from AD.
Now I need to enable users to get access to some files which are located on file share, where each user has his own folder.
First proof of concept works like this:
appPool in IIS is configured to run under some domain user, and this user was given R/W access to file share and all user folders
when the user logs into web app only content of the folder on the path "\\myFileServer\username" is visible to him. And same when uploading files they get stored to "\\myFileServer\username".
While this works, doesn't seem to be secure at all. First issue is that user under which application pool runs has access to folders from all users. And even bigger concern is that only username determines to which folder you have access.
So my question is what is the correct/better way to doing this ? I was reading about impersonating the user, but this is not advised anymore if I understood correctly ? And I don't have Windows authentications since the web application must be accessible from internet.
I recommend not running the application under a user account, but creating an application specific account under which it runs with the proper R/W rights, and separate the person who gives these rights from the development team.
Within the application's authentication: after you receive a GET/POST request, you can verify the path to which the current user would read/write data, and cross-reference this with the path the user is authorized to read/write from. If these are incorrect, return a 401 NOT AUTHORIZED response, else, carry on the operation as you do now.
If your endpoints are protected properly, and the application runs under its own account, I don't see any harm in the setup itself. This still however gives the developers a way, through the application, to indirectly access other user's files. Based on how tight these checks must be, you could add additional controls, (like only allowing the application to connect from the production server, and only allowing server transport in a controlled way).
From the Description of your Problem i think Custom HttpHandlers are the right choice for you. You didn't mention what type of files will be present in your Folder , for brevity i will answer by assuming it will be having PDF files.
As you were mentioning that your application will be having different users so for this you need to use .NET built-in authentication manager and role provider. With a simple security framework setup, we'll place a PDF file in the web application, behind a web.config protected folder.then create a custom HTTP handler to restrict access on the static document to only those users who should be allowed to view it.
A sample HTTP Handler:
public class FileProtectionHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
switch (context.Request.HttpMethod)
{
case "GET":
{
// Is the user logged-in?
if (!context.User.Identity.IsAuthenticated)
{
FormsAuthentication.RedirectToLoginPage();
return;
}
string requestedFile =
context.Server.MapPath(context.Request.FilePath);
// Verify the user has access to the User role.
if (context.User.IsInRole("User"))
{
SendContentTypeAndFile(context, requestedFile);
}
else
{
// Deny access, redirect to error page or back to login
//page.
context.Response.Redirect("~/User/AccessDenied.aspx");
}
break;
}
}
}
Method SendContentTypeAndFile :
private HttpContext SendContentTypeAndFile(HttpContext context, String strFile)
{
context.Response.ContentType = GetContentType(strFile);
context.Response.TransmitFile(strFile);
context.Response.End();
return context;
}
private string GetContentType(string filename)
{
// used to set the encoding for the reponse stream
string res = null;
FileInfo fileinfo = new FileInfo(filename);
if (fileinfo.Exists)
{
switch (fileinfo.Extension.Remove(0, 1).ToLower())
{
case "pdf":
{
res = "application/pdf";
break;
}
}
return res;
}
return null;
}
Last step is that you need to configure this HTTP Handler in the webconfig ,
and You can see the more info here
Here is the complete Source Code
You're architecture (and assumptions) seem good for a low/mid security level, but if the nature of your data is very sensitive (medical, etc) my biggest concern about security would be controlling the user sessions.
If you're using forms authentication then you're storing the authenticated identity in a cookie or in a token (or if you're using sticky sessions then you're sending the session Id, but for the case it's the same). The problem arises if user B has phisical access to the machine where user A works. If user A leaves it's workplace (for a while or forever) and he doesn't explicitly close it's session in your web app, then his identity has been left around, at least until his cookie/token expires, and user B can use it since the identity system of ASP.NET hasn't performed a SignOut. The problem is even worse if you use tokens for authorization, because in all the infamous Microsoft implementations of the Identity System you're responsible of providing a way to invalidate such tokens (and make them dissapear from the client machine) when the user signs out, since they would stay valid until it's expiration. This can be addressed (but no completely thus not very satisfactorily for high security requirements) issuing short living refresh tokens, but that's another story, and I don't know if it's your case. If you're going with cookies then when user A signs out it's cookie is invalidated and removed from the request/response cicle, so this problem is mitigated. Anyway you should ensure that your users close their sessions in your web app or/and configure the cookies with short lives or short sliding expirations.
Other security concerns may be related with CSRF, wich you can prevent using the Antiforgery Token infrastructure of ASP.NET, but these kind of attacks are methods that are very far away from the tipical user (I don't know anything about the nature of your user and if your app is exposed to public on internet or it's only accesible on an intranet), but If you worry for such specialised attacks and have so sensitive data, maybe you should go with something more complex than forms authentication (two factor, biometrical, etc)

ASP.NET 3.5 Multiple Role Providers

I have an ASP.NET 3.5 application that I recently extended with multiple membership and role providers to "attach" a second application within this application. I do not have direct access to the IIS configuration, so I can't break this off into a separate application directory.
That said, I have successfully separated the logins; however, after I login, I am able to verify the groups the user belongs to through custom role routines, and I am capable of having identical usernames with different passwords for both "applications."
The problem that I am running into is when I create a user with an identical username to the other membership (which uses web.config roles on directories), I am able to switch URLs manually to the other application, and it picks up the username, and loads the roles for that application. Obviously, this is bad, as it allows a user to create a username of someone who has access to the other application, and cross into the other application with the roles of the other user.
How can I mitigate this? If I am limited to one application to work with, with multiple role and membership providers, and the auth cookie stores the username that is apparently transferable, is there anything I can do?
I realize the situation is not ideal, but these are the imposed limitations at the moment.
Example Authentication (upon validation):
FormsAuthentication.SetAuthCookie(usr.UserName, false);
This cookie needs to be based on the user token I suspect, rather than UserName in order to separate the two providers? Is that possible?
Have you tried specifying the applicationName attribute in your membership connection string?
https://msdn.microsoft.com/en-us/library/6e9y4s5t.aspx?f=255&MSPPError=-2147217396
Perhaps not the answer I'd prefer to go with, but I was able to separate the two by having one application use the username for the auth cookie, and the other use the ProviderUserKey (guid). This way the auth cookie would not be recognized from one "application" to the other.
FormsAuthentication.SetAuthCookie(user.ProviderUserKey.ToString(), false);
This required me to handle things a little oddly, but it simply came down to adding some extension methods, and handling a lot of membership utilities through my own class (which I was doing anyhow).
ex. Extension Method:
public static string GetUserName(this IPrincipal ip)
{
return MNMember.MNMembership.GetUser(new Guid(ip.Identity.Name), false).UserName;
}
Where MNMember is a static class, MNMembership is returning the secondary membership provider, and GetUser is the standard function of membership providers.
var validRoles = new List<string>() { "MNExpired", "MNAdmins", "MNUsers" };
var isValidRole = validRoles.Intersect(uroles).Any();
if (isValidRole)
{
var userIsAdmin = uroles.Contains("MNAdmins");
if (isAdmin && !userIsAdmin)
{
Response.Redirect("/MNLogin.aspx");
}
else if (!userIsAdmin && !uroles.Contains("MNUsers"))
{
Response.Redirect("/MNLogin.aspx");
}...
Where isAdmin is checking to see if a subdirectory shows up in the path.
Seems hacky, but also seems to work.
Edit:Now that I'm not using the username as the token, I should be able to go back to using the web.config for directory security, which means the master page hack should be able to be removed. (theoretically?)
Edit 2:Nope - asp.net uses the username auth cookie to resolve the roles specified in the web.config.

AuthenticationProperties.RedirectUri is not passed to Google in Challenge()

Within my web application I have registered Google as a single sign-on provider:
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions {
ClientId = "8765.......apps.googleusercontent.com",
ClientSecret = "Secret"
})
My app doesn't allow users to sign-up/register (instead their accounts are created by an administrator, but they can later link their account up with Google).
In my "Sign in with Google" controller, I am trying to issue a Challenge() to redirect to Google. This might not be thecorrect approach:
string redirectUri = "http://localhost:55262/SSO/Google/ProcessToken"; // actually created in code, but shown as string for clarity
AuthenticationProperties properties = new AuthenticationProperties();
properties.RedirectUri = Server.UrlEncode(redirectUri);
Context.GetOwinContext().Authentication.Challenge(properties, "Google");
This correctly sends the user to Google, but Google then presents Error: redirect_uri_mismatch, saying that:
The redirect URI in the request: http://localhost:55262/signin-google
did not match a registered redirect URI.
I've seen this error before when the return URI collection in the Google control panel does not contain the redirect_uri specified.
If I debug in VS2015, I can see the redirect_uri property being set correctly in the AuthenticationProperties, but it seems that OWIN/Katana is not passing it to Google. Instead, when I hit Google, the return_uri is the default one used by OWIN/Katana. The one I set is being ignored.
The Google request details seem to confirm this:
scope=openid profile email
response_type=code
redirect_uri=http://localhost:55262/signin-google
What am I doing wrong here please? Should I not be using Challenge() to allow users to link up their local application account with Google?
To provide additional information on the accepted answer...
Its okay to ignore /signin-google
It emerges that the /signin-google URI is internally-managed by OWIN/Katana. You, as a developer, do not need to be concerned by it, but you do need to add it in the Google developer console as an Authorized redirect URI.
In the Google request, note that OWIN always passes the redirect URI to Google as /signin-google, regardless of what custom URI you set in the AuthenticationProperties.RedirectUri property. Although at first this may seem like a bug/problem, it has a major advantage in that OWIN can manage all callbacks via a single callback URI. Your callback URI is not forgotten about either (see below)!.
So what about your own redirect URL?
Well, that's where the AuthenticationProperties() come into play. By specifying your own callback URL like so...
AuthenticationProperties properties = new AuthenticationProperties { RedirectUri = "https://my.app.com/custom/callback/uri" };
...after OWIN has examined the Google token and extracted the necessary details, the user is then redirected to your specified URL.
This was where I was getting confused, as I didn't understand what to do with /signin-google, when in actual fact no action was taken. This applies to both MVC and webforms - you do not need to concern yourself with what gets passed to Google. However, if using webforms, or specifying authorization rules in web.config, you will need this to prevent returning users hitting the logging page again:
<location path="signin-google">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
Here is all the code you need to send the user to Google, and return the token containing their details:
Outbound
Send the user to Google from a controller, button click event, page load, anything (regardless of your ASP/hosting stack):
// set the callback, for after OWIN finishes examining what comes back from Google
AuthenticationProperties properties = new AuthenticationProperties { RedirectUri = "https://www.myapp.com/some/callback/uri" };
// send the user to Google
Context.GetOwinContext().Authentication.Challenge(properties, "Google");
// Stop execution of the current page/method - the 401 forces OWIN to kick-in and do its thing
Response.StatusCode = 401;
Response.End();
Inbound
The user is returned from Google after validating their identity
Microsoft.AspNet.Identity.Owin.ExternalLoginInfo loginInfo = Context.GetOwinContext().Authentication.GetExternalLoginInfo();
Note that the OWIN's Open Authentication have predefined methods. In another words, in localhost:port/signin-google, the OWIN awaits for calling the signin-google by the external authentication service (Although you can't find its implementation inside the project). The signin-google is a valid and working path and I prefoundly exhort you not to change it (due to avoid writing a new implementation as a controller action).
I had similar trouble, After spending many weary days, finally, I found out the problem comes from the original user's URL which is effective on the sent redirect_uri by the OWIN. Clearly:
If you type www.site.com → redirect_uri equals to
www.site.com/signin-google
If you type site.com → redirect_uri equals to
site.com/signin-google
And Google will return redirect_uri_mismatch Error for one of the above cases based on entered redirect URLs in Console. I think your problem comes from this reality too and the solution is setting any possible URLs in console.

Issue with https on production environment

I have tried many options and this is my last resort to see if any of the community members have any ideas.
I have .NET MVC 5 application in which I use a Filter to force HTTPS on each unsecured request.
Here is the scenario:
Access my application at say, http://portal.mywebsite.com
It is redirected to third party (auth0) SSO provider for authentication. If the user is not already authenticated, he is redirected to the SSO login page.
The user enters valid credentials, authenticated.
The above scenario works perfectly. But the issue is If I access the same application with https say https://portal.mywebsite.com, it fails. To be precise, it fails to retrieve a ExternalIdentity (ExternalCookie) on the server.
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (externalIdentity == null)
{
throw(new Exception("Could not get the external identity. Please check your Auth0 configuration settings and ensure that " +
"you configured UseCookieAuthentication and UseExternalSignInCookie in the OWIN Startup class. " +
"Also make sure you are not calling setting the callbackOnLocationHash option on the JavaScript login widget."));
}
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, CreateIdentity(externalIdentity));
return RedirectToLocal(returnUrl);
}
Also, accessing the application with https on my test environment works and not the production environment.
All my web applications are hosted as Azure WebRoles.
I tried Fiddler to watch the requests between working and non-working to see if I can find any useful information in identifying the issue but no success.
Any thoughts or ideas that I could try to help me narrow down the cause?
Thanks in advance!
There is a bug in Microsoft's Owin implementation for System.Web. The temporary fix is addressed here at github.com/KentorIT/owin-cookie-saver
Someone had the same issue .AspNetApplicationCookie and ASP.NET_SessionId not created

In ASP.NET; How can I login from a remote HTML page into another ASPX page with ASP.NET Membership

I basically need to login into another domain that is using asp.net membership.
If we have an ASP.net web application using ASP.Net Membership on one hand, and
an HTML page from another domain on the other hand.
Is it possible to login into the ASP.NET website via remote HTML page.
I've done this with Coldfusion before but ASP.NET membership is using a server control.
Cheers!
Underneath the Login Server Control, ASP.NET uses a MembershipProvider implementation and Forms Authentication to a user in with ASP.NET Membership. You can replicate these steps without using the Login Server Control, by manually validating the credentials and then attaching the FormsAuthentication cookie to the Response.
Here are some resources that should help you get started:
Understanding the Forms Authentication Ticket and Cookie - MSDN
Explained: Forms Authentication in ASP.NET 2.0 - MSDN
Examining ASP.NET's Membership, Roles, and Profile - 4guysfromrolla
You would also probably benefit from Reflecting on the source of the Login control, so you can gain an understanding the exact sequence of events that happens when a user logs in using the server control. This should make it easier for you to understand how to replicate that functionality for your particular use case.
As a side-note, I would recommend using a custom IHttpHandler implementation as an injection point for processing the login request, but there are many ways you can accomplish this task.
Update, I'm feeling generous, so
Below is an example handler that you could use to log a user in with ASP.NET Membership and FormsAuthentication (just like the server control).
This code assumes:
There is a mapping configured with either Routing or the web.config that will call this handler.
The requesting page has a form that points to the url/route that is mapped in the web.config or with routing, and that the form on that page contains a username input field with the name username and a password input field with the name password.
public class LoginHandler : IHttpHandler
{
void IHttpHandler.ProcessRequest(HttpContext context)
{
string username = context.Request["username"];
string password = context.Request["password"];
if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password) && Membership.Provider.ValidateUser(username, password))
{
FormsAuthentication.SetAuthCookie(username, true);
RenderUserLoggedInResponse(context.Response,username);
}
else FormsAuthentication.RedirectToLoginPage("loginfailure=1");
}
private static void RenderUserLoggedInResponse(HttpResponse response, string username)
{
response.Write(string.Format("You have logged in successfully, {0}!", username));
response.End();
}
bool IHttpHandler.IsReusable { get { return true; } }
}

Resources