I'm currently building a new ASP.NET MVC 5 project with a private section dedicated for administrators.
This section is developed in a modular way and have its own authentication (using ASP.NET identity)
I also want a restricted section for customers in the same web site with a different authentication (using ASP.NET identity too).
Is it possible to have different User.Identity objects depending on the location of the website ?
(ex: "ClientUser" at the root of the website and "ExtranetUser" if we go to /Extranet)
Here's the code I currently have for both sections :
Client section (main ASP.NET application) :
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "ClientCookie",
LoginPath = new PathString("/Account/LoginRegister"),
ExpireTimeSpan = TimeSpan.FromHours(4),
Provider = new CookieAuthenticationProvider()
});
}
Extranet section (stored in the module project) :
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "ExtranetCookie",
LoginPath = new PathString("/Extranet/Account/Login"),
Provider = new CookieAuthenticationProvider()
});
}
Thanks !
Of course you can have several DbContext (or IdentityDbContext) elements in your application. The problem is that during startup you need to provide a database and UserManager as of CreatePerOwinContext. Event this can be handled as shown in this post.
Define your two User and SignIn manager classes:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Add our custom managers
app.CreatePerOwinContext<CustomUserManager>(CustomUserManager.Create);
app.CreatePerOwinContext<CustomSignInManager>(CustomSignInManager.Create);
}
And refer in the corresponding controllers the correct implementation:
private CustomSignInManager _signInManager;
public CustomSignInManager CustomSignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<CustomSignInManager>();
}
private set { _signInManager = value; }
}
I didn't try by myself but the approch looks smart.
Related
The reason behind my question is that, there is a beginner developer team at a company, starting to create a new business project after finishing some vital courses for web applications.
The aim is to have a Web Application within the company's intranet in the following form:
On Angular SPA frontend with ASP.NET Core WebAPI, using Entity Framework Core with a Microsoft SQL Server database running on Windows Server.
The current authentication method of course is Windows Authentication.
In order to create proper auth services, it was suggested to use JWT Web Token, however it is hard to tell whether there is a better approach for using authentication by combining them on the above mentioned Web Application.
As we are lacking of experience, a review of any familiars' would be highly appreciated in this matter!
The current authentication method of course is Windows Authentication.
In order to create proper auth services, it was suggested to use JWT
Web Token.
As you may know JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.Therefore, JWT creates a JSON web token and encodes, sterilizes, and adds a signature with a secret key that cannot be tampered with; Thus, it would ensure your application security well.
It is hard to tell whether there is a better approach for using
authentication by combining them on the above mentioned Web
Application.
Depending on your current application eco-system you could use Jwt without any concern as you have SPAs and other application running on intranet. While, implementing jwt it would allow you to ensure your authentication regardless of any platform. For instance, It could be windows app, SPA or any cross platform app. You can authenticate all the platfroms using this infrastructure.
As we are lacking of experience, a review of any familiars' would be
highly appreciated in this matter!
Considering your scenario, here is the implementaion steps, you could follow. You always can customize it based on your requirement. Altough, I am share you the basic steps which might assist you.
Note:
Following implementation, can be used either in any internal(intranet) or public web application(internet app) in any platforms.
Implementaion Guideline:
appsettings.json:
"Jwt": {
"Key": "Set_Your_SecretKey",
"Issuer": "YourApplication_URL"
}
Jwt Token View Model:
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[DataType(DataType.Password)]
public string Password { get; set; } = string.Empty;
}
Jwt Token Interface:
public interface IAuthenticationRepository
{
Task<TokenViewModel> AuthenticateLogin(LoginViewModel loginInfo);
}
Repository Implementation:
public class AuthenticationRepository : IAuthenticationRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly IConfiguration _config;
public AuthenticationRepository(ApplicationDbContext dbContext, IMapper mapper, IConfiguration config)
{
_dbContext = dbContext;
_mapper = mapper;
_config = config;
}
public async Task<TokenViewModel> AuthenticateLogin(LoginViewModel loginInfo)
{
try
{
var isAuthenticate = await _dbContext.Users.FirstOrDefaultAsync(u => u.UserEmail == loginInfo.Email && u.Password == loginInfo.Password);
var tokenViewModel = new TokenViewModel();
if (isAuthenticate != null)
{
var getToken = GenerateJSONWebToken(loginInfo);
tokenViewModel = _mapper.Map<TokenViewModel>(isAuthenticate);
tokenViewModel.Token = getToken;
}
return tokenViewModel;
}
catch (Exception ex)
{
throw;
}
}
private string GenerateJSONWebToken(LoginViewModel userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Email),
new Claim(JwtRegisteredClaimNames.Email, userInfo.Password),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
Response Model I Have Used:
public class ResponseViewModel
{
public string output { get; set; }
public string msg { get; set; }
public object apiResponse { get; set; }
}
Auth Controller:
[Route("api/Authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAuthenticationRepository _authenticationService;
public AuthenticationController(IAuthenticationRepository authenticationService)
{
this._authenticationService = authenticationService;
}
[AllowAnonymous]
[Route("login")]
[HttpPost]
public async Task<IActionResult> LoginAsync([FromBody] LoginViewModel loginInfo)
{
IActionResult response = Unauthorized();
var user = await _authenticationService.AuthenticateLogin(loginInfo);
if (user.Token != null)
{
response = Ok(new ResponseViewModel { output = "success", msg = "Login Successfully", apiResponse = user });
}
return response;
}
Authenticate Your Access:
Once you have successfully generate jwt auth token, now you can pass that as Bearer token for any authorization and to restrict access you can use [Authorize] before any resource where you wants to restrict access.
Output:
Note: If you would like to know more details on jwt token you could check our official document here
I have a webpage which uses multiple URLS for the same application:
for example:
*.MyWebPage.com.au
*.YourWebPage.com.au
So it will use subdomains on multiple urls. The problem is I need to allow for the user to be authenticated on all subdomains of the url which they have logged into.
For example, if they login via www.mywebpage.com.au the cookie needs to be set for *.mywebpage.com.au or if they login via www.yourwebpage.com.au the cookie should be *.yourwebpage.com.au.
Most of the documentation in allowing subdomains for ASP.NET core identity points to the startup.cs (or startup.auth.cs) file and entering something like this:`
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieDomain = "mywebpage.com.au"
});`
this will not work for me because I dont want a fixed domain, I just want to allow for all the users to have access to all the subdomains for the url they have signed in at. I can obviously get their url at the time of login via the request, but I need to dynamically set the cookiedomain at this point.
What I didnt realise when I started was the difference between Identity and CookieAuthentication.
Since I was using Identity
app.UseIdentity();
app.UseCookieAuthentication was not the solution.
I finally found my solution by implementing ICookieManager.
Here is my solution:
in Startup.cs:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 5;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Cookies.ApplicationCookie.CookieManager = new CookieManager(); //Magic happens here
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
now in a class I have called CookieManager.cs:
public class CookieManager : ICookieManager
{
#region Private Members
private readonly ICookieManager ConcreteManager;
#endregion
#region Prvate Methods
private string RemoveSubdomain(string host)
{
var splitHostname = host.Split('.');
//if not localhost
if (splitHostname.Length > 1)
{
return string.Join(".", splitHostname.Skip(1));
}
else
{
return host;
}
}
#endregion
#region Public Methods
public CookieManager()
{
ConcreteManager = new ChunkingCookieManager();
}
public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
options.Domain = RemoveSubdomain(context.Request.Host.Host); //Set the Cookie Domain using the request from host
ConcreteManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
ConcreteManager.DeleteCookie(context, key, options);
}
public string GetRequestCookie(HttpContext context, string key)
{
return ConcreteManager.GetRequestCookie(context, key);
}
#endregion
In addition to #michael's solution:
ICookie: ICookie Interface is an abstraction layer on top of http cookie object, which secures the data.
ICookieManager: Cookie Manager is an abstraction layer on top of ICookie Interface. This extends the Cookie behavior in terms of <TSource> generic support, Func<TResult>.This is implemented by DefaultCookieManager class. ICookie Interface is a depedenacy of this class.
Usage of CookieManager:
Add CookieManager in startup Configure Service.
Access the CookieManager API.
And the source code is available on git by Nemi Chand.
How many main domains are there? If there are not too many, you can add several CookieAuthenticationOptions. Like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "mywebpage.com.au",
CookieDomain = "mywebpage.com.au",
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "yourwebpage.com.au",
CookieDomain = "yourwebpage.com.au",
});
If there are too many main domains, you will need to write your own cookie provider.
Addition to #michael's answer:
How to "handle the deletecookie event, adding options.Domain = RemoveSubdomain(context.Request.Host.Host)":
just add
options.Domain= RemoveSubdomain(context.Request.Host.Host);
before
ConcreteManager.DeleteCookie(context, key, options);
in
CookieManager.DeleteCoockie(..){..};
And don't forget to call CookieManager.DeleteCoockie on logout!
PS Also, if you need to be able to login both on subdomain.example.com and on example.com - you need to modify AppendResponseCookie(..){..}, or you will get only TLD (.com/.ru etc) here
I'm using cookie authentication with OWIN in a .NET MVC 4.5 setup. I set up the cookie authentication configuration in Startup.Auth.cs (code below) and I would like to access the LoginPath that I set in CookieAuthenticationOptions in a controller so that if, for whatever reason, my LoginPath changes, I only need to change it in one place. So just looking for something like context.GetCookieAuthenticationOptions().LoginPath Is there a way to access the CookieAuthenticationOptions outside of Startup.Auth.cs, or is my only option here to do something like add an appSetting in Web.config and then use that instead?
Startup.Auth.cs code, I would like to access LoginPath outside of this file.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("Login"),
SlidingExpiration = true,
ExpireTimeSpan = _expirationTimeSpan,
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))
},
});
There is no direct way to do this. If you look closely, the cookie options object is stored in the AppBuilder class private _middleware collection. There is no way to access this property (except reflection).
You can however store the cookieOptions object in the Owin context:
var cookieOptions = new CookieAuthenticationOptions
{
// ...
LoginPath = new PathString("/Account/Login"),
// ...
};
app.UseCookieAuthentication(cookieOptions);
app.CreatePerOwinContext(() => new MyCookieAuthOptions(cookieOptions));
In the controller you can then access it like this:
var cookieOptions = HttpContext.GetOwinContext().Get<MyCookieAuthOptions>().Options;
Owin context only supports IDisposable object, therefore we need to wrap CookieAuthenticationOptions in a IDisposable object:
public class MyCookieAuthOptions : IDisposable
{
public MyCookieAuthOptions(CookieAuthenticationOptions cookieOptions)
{
Options = cookieOptions;
}
public CookieAuthenticationOptions Options { get; }
public void Dispose()
{
}
}
I am setting up bearer token authentication in Web API 2, and I don't understand how (or where) the bearer token is being stored server-side. Here is the relevant code:
Startup:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
}
}
WebApiConfig:
public class WebApiConfig
{
public static void ConfigureWebApi()
{
Register(GlobalConfiguration.Configuration);
}
public static void Register(HttpConfiguration http)
{
AuthUtil.ConfigureWebApiToUseOnlyBearerTokenAuthentication(http);
http.Routes.MapHttpRoute("ActionApi", "api/{controller}/{action}", new {action = Actions.Default});
}
}
AuthUtil:
public class AuthUtil
{
public static string Token(string email)
{
var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, email));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
return token;
}
public static void ConfigureWebApiToUseOnlyBearerTokenAuthentication(HttpConfiguration http)
{
http.SuppressDefaultHostAuthentication();
http.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
}
}
LoginController:
public class LoginController : ApiController
{
...
public HttpResponseMessage Post([FromBody] LoginJson loginJson)
{
HttpResponseMessage loginResponse;
if (/* is valid login */)
{
var accessToken = AuthUtil.Token(loginJson.email);
loginResponse = /* HTTP response including accessToken */;
}
else
{
loginResponse = /* HTTP response with error */;
}
return loginResponse;
}
}
Using the above code, I'm able to login and store the bearer token client-side in a cookie, and then make calls to controllers marked with [Authorize] and it lets me in.
My questions are:
Where / how is the bearer token being stored server-side? It seems like this is hapenning through one of the OWIN calls but I can't tell where.
Is it possible to persist the bearer tokens to a database server-side so that they can remain in place after a Web API server restart?
If the answer to #2 is no, is there anyway for a client to maintain its bearer token and re-use it even after the Web API goes down and comes back up? While this may be rare in Production, it can happen quite often doing local testing.
They're not stored server side -- they're issued to the client and the client presents them on each call. They're verified because they're signed by the owin host's protection key. In SystemWeb hosting, that protection key is the machineKey setting from web.config.
That's unnecessary, as long as the protection key the owin host uses doesn't change across server restarts.
A client can hold onto a token for as long as the token is valid.
For those who are looking for how to set web.config, here is a sample
<system.web>
<machineKey validation="HMACSHA256" validationKey="64-hex"
decryption="AES" decryptionKey="another-64-hex"/>
</system.web>
You need both validationKey and decriptionkey to make it work.
And here is how to generate keys
https://msdn.microsoft.com/en-us/library/ms998288.aspx
To add to this, the token can be persisted server side using the SessionStore property of of CookieAuthenticationOptions. I wouldn't advocate doing this but it's there if your tokens become excessively large.
This is an IAuthenticationSessionStore so you could implement your own storage medium.
By default the token is not stored by the server. Only your client has it and is sending it through the authorization header to the server.
If you used the default template provided by Visual Studio, in the Startup ConfigureAuth method the following IAppBuilder extension is called: app.UseOAuthBearerTokens(OAuthOptions).
I'm trying to implement an MVC4 web application with the following requirements:
(a) it offers its services to authenticated users only. As for authentication, I'd like to use simple membership, as it is the latest authentication technique from MVC, gives me the advantage of defining my own db tables, provides OAuth support out of the box, and is easily integrated with both MVC and WebApi.
(b) it exposes some core functions via WebApi for mobile/JS clients, which should be authenticated via basic HTTP authentication (+SSL). Typically I'll have JS clients using jQuery AJAX calls to WebApi controllers, decorated with the Authorize attribute for different user roles.
(c) ideally, in a mixed environment I would like to avoid a double authentication: i.e. if the user is already authenticated via browser, and is visiting a page implying a JS call to a WebApi controller action, the (a) mechanism should be enough.
Thus, while (a) is covered by the default MVC template, (b) requires basic HTTP authentication without the mediation of a browser. To this end, I should create a DelegatingHandler like the one I found in this post: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers.
The problem is that its implementation requires some way of retrieving an IPrincipal from the received user name and password, and the WebSecurity class does not provide any method for this (except Login, but I would avoid changing the logged user just for the purpose of authorization, also because of potential "mixed" environments like (c)). So it seems my only option is giving up simple membership. Does anyone have better suggestions? Here is the relevant (slightly modified) code from the cited post:
public interface IPrincipalProvider
{
IPrincipal GetPrincipal(string username, string password);
}
public sealed class Credentials
{
public string Username { get; set; }
public string Password { get; set; }
}
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
public IPrincipalProvider PrincipalProvider { get; private set; }
public BasicAuthMessageHandler(IPrincipalProvider provider)
{
if (provider == null) throw new ArgumentNullException("provider");
PrincipalProvider = provider;
}
private static Credentials ParseAuthorizationHeader(string sHeader)
{
string[] credentials = Encoding.ASCII.GetString(
Convert.FromBase64String(sHeader)).Split(new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) ||
String.IsNullOrEmpty(credentials[1])) return null;
return new Credentials
{
Username = credentials[0],
Password = credentials[1],
};
}
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter))
{
Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
Thread.CurrentPrincipal = PrincipalProvider
.GetPrincipal(parsedCredentials.Username, parsedCredentials.Password);
}
}
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized
&& !response.Headers.Contains(BasicAuthResponseHeader))
{
response.Headers.Add(BasicAuthResponseHeader,
BasicAuthResponseHeaderValue);
}
return response;
});
}
}
Here is another solution that meets all of your requirements. It uses SimpleMemberhsip with a mix of forms authentication and basic authentication in an MVC 4 application. It can also support Authorization, but it is not required by leaving the Role property null.
Thank you, this seems the best available solution 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 an MVC controller restricted action, I am redirected to the login page as expected to get authenticated.
2) when I trigger a jQuery ajax call to a WebApi controller restricted action, the call succeeds (except of course when not using SSL).
Yet, it does not work when after logging in in the website, the API call still requires authentication. Could anyone explain what's going here? In what follows I detail my procedure as I think it might be useful for starters like me.
Thank you (sorry for the formatting of what follows, but I cannot manage to let this editor mark code appropriately...)
Procedure
create a new mvc4 app (basic mvc4 app: this already comes with universal providers. All the universal providers class names start with Default...);
customize web.config for your non-local DB, e.g.:
<connectionStrings>
<add name="DefaultConnection"
providerName="System.Data.SqlClient"
connectionString="data source=(local)\SQLExpress;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True" />
Also it's often useful to set a machineKey for hashing passwords, so that you can freely move this site around from server to server without having your passwords scrambled. Use a machine key generator website to define an entry like this:
<system.web>
<machineKey
validationKey="...thekey..."
decryptionKey="...thekey..."
validation="SHA1"
decryption="AES" />
if required create a new, empty database corresponding to the connection string of your web.config. Then start our good old pal WSAT (from VS Project menu) and configure security by adding users and roles as required.
if you want to, add a HomeController with an Index action, because no controller is present in this template and thus you could not test-start your web app without it.
add Thinktecture.IdentityModel.45 from NuGet and add/update all your favorite NuGet packages. Notice that at the time of writing this, jquery validation unobtrusive from MS is no more compatible with jQuery 1.9 or higher. I rather 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 bundles (App_Start/BundleConfig.cs).
modify the WebApiConfig.cs in App_Start by adding it the code after the DefaultApi route configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
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, the api controllers will be placed under Controllers/Api/, so create this folder.
Add to models a LoginModel.cs:
public class LoginModel
{
[Required]
[Display(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 a StringResources.resx resource (with code generation) I usually place under an Assets folder, with the 3 strings quoted in the attributes.
Add a ClaimsTransformer.cs to your solution root, like this:
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 your app root namespace):
<configSections>
<section name="system.identityModel"
type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
...
add the other models for account controller, with their views (you can derive them from MVC3 application template, even if I prefer changing them to more localizable-friendly variants using attributes requiring string resource names rather than literals).
to test browser-based authentication, add some [Authorized] action to a controller (e.g. HomeController), and try accessing it.
to test basic HTTP authentication, insert in some view (e.g. Home/Index) a code like this (set your user name 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 jQuery plugin for encoding/decoding Base64: jquery.base64.js and its minified counterpart.
To allow SSL, follow the instructions here: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx (basically, enable SSL in the web project properties and connect to the port specified in the property value).
Maybe this helps - sounds this is like your scenario:
http://leastprivilege.com/2012/10/23/mixing-mvc-forms-authentication-and-web-api-basic-authentication/
http://leastprivilege.com/2012/10/24/extensions-to-the-web-apimvc-formsbasic-auth-sample-claims-transformation-and-ajax/