Thank you, this seems like the best solution available at this time! I managed to create a dummy solution from scratch (find it here: http://sdrv.ms/YpkRcf ), and it seems to work in the following cases:
1) when I try to access the limited action of the MVC controller, I am redirected to the login page, as expected, to authenticate.
2) when I launch a jQuery ajax call for the limited action of the WebApi controller, the call succeeds (except, of course, if SSL is not used).
However, it does not work when, after entering the website, the API call still requires authentication. Can someone explain what is going on here? In the future, I describe my procedure in detail, because, in my opinion, this can be useful for beginners such as myself.
Thanks (sorry for formatting what follows, but I can't let this editor mark the code correctly ...)
Procedure
create a new mvc4 application (the basic mvc4 application: these are already universal providers. All class names of universal providers begin with the default value ...);
configure web.config for your non-local database, for example:
<connectionStrings> <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="data source=(local)\SQLExpress;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True" />
It is also often useful to set machineKey for password hashing so that you can freely move this site from server to server without using your passwords. Use the key generator website to determine the following entry:
<system.web> <machineKey validationKey="...thekey..." decryptionKey="...thekey..." validation="SHA1" decryption="AES" />
if necessary, create a new, empty database corresponding to the connection string of your web.config. Then run our good old WSAT (from the VS Project menu) and configure security by adding users and roles as needed.
if you want, add a HomeController with an index action, because there is no controller in this template, and you cannot run its web application without it.
add Thinktecture.IdentityModel.45 from NuGet and add / update all your favorite NuGet packages. Please note that at the time of this writing, jquery validation, unobtrusive from MS, is no longer compatible with jQuery 1.9 or higher. I prefer to use http://plugins.jquery.com/winf.unobtrusive-ajax/ . So, remove jquery.unobtrusive * and add this library (which consists of winf.unobtrusive-ajax and additional methods) in your packages (App_Start / BundleConfig.cs).
change WebApiConfig.cs in App_Start, adding it after configuring the DefaultApi route:
public static class WebApiConfig {public static void Register (HttpConfiguration) {config.Routes.MapHttpRoute (name: "DefaultApi", routeTemplate: "api / {controller} / {id}", defaults: new {id = RouteParameter.Optional} );
// added for Thinktecture var authConfig = new AuthenticationConfiguration { InheritHostClientIdentity = true, ClaimsAuthenticationManager = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager }; // setup authentication against membership authConfig.AddBasicAuthentication((userName, password) => Membership.ValidateUser(userName, password)); config.MessageHandlers.Add(new AuthenticationHandler(authConfig)); }
}
To be cleaner, api controllers will be placed under Controllers / Api /, so create this folder.
Add to LoginModel.cs models:
public class LoginModel {[Required] [Show (Name = "UserName", ResourceType = typeof (StringResources))] public string UserName {get; set; }
[Required] [DataType(DataType.Password)] [Display(Name = "Password", ResourceType = typeof(StringResources))] public string Password { get; set; } [Display(Name = "RememberMe", ResourceType = typeof(StringResources))] public bool RememberMe { get; set; }
}
This model requires the StringResources.resx resource (with code generation), which I usually place in the Assets folder with three lines specified in the attributes.
Add ClaimsTransformer.cs to your solution root, for example:
public class ClaimsTransformer: ClaimsAuthenticationManager {public override ClaimsPrincipal Authenticate (string resourceName, ClaimsPrincipal incomingPrincipal) {if (! incomingPrincipal.Identity.IsAuthenticated) {return base.Authenticate (resourceName, incomingPrincipal); }
var name = incomingPrincipal.Identity.Name; return Principal.Create( "Custom", new Claim(ClaimTypes.Name, name + " (transformed)")); }
}
Add Application_PostAuthenticateRequest to Global.asax.cs:
public class MvcApplication: HttpApplication {... protected void Application_PostAuthenticateRequest () {if (ClaimsPrincipal.Current.Identity.IsAuthenticated) {var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager; var newPrincipal = transformer.Authenticate (string.Empty, ClaimsPrincipal.Current);
Thread.CurrentPrincipal = newPrincipal; HttpContext.Current.User = newPrincipal; } }
}
web.config (replace YourAppNamespace with the application namespace):
<configSections> <section name = "system.identityModel" type = "System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = B77A5C561934E089" /> ...
add other models for the account controller with their representations (you can get them from the MVC3 application template, even if I prefer to change them to more accessible options for localization, using attributes that require line names rather than literals).
to test browser-based authentication, add some [Authorized] actions to the controller (for example, HomeController) and try to access it.
to test basic HTTP authentication, insert the following code in some form (for example, Home / Index) (specify your username and password in the token variable):
...
<p> Test call
$ (function () {$ ("# test"). click (function () {var token = "USERNAME: PASSWORD"; var hash = $ .base64.encode (token); var header = "Basic" + hash; console.log (header); $.ajax({ url: "/api/dummy", dataType: "json", beforeSend: function(xhr) { xhr.setRequestHeader("Authorization", header); }, success: function(data) { alert(data); }, error: function(jqXHR, textStatus, errorThrown) { alert(errorThrown); } }); }); });
This requires the Base64 jQuery encoding / decoding plugin: jquery.base64.js and its miniature instance.
To enable SSL, follow the instructions here: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx (basically, enable SSL in the properties of the web project and connect to the specified port in the property value).