I am developing a website using ASP.NET MVC with an API using ServiceStack.
Very soon I want to add authentication. The website will have at least two types of users 'service providers' and 'service consumers', although a user could have multiple roles.
I am open to using new MVC Identity, but I want whatever I use to work nicely for both the servicestack API and MVC 'pages' that don't necessarily use the API but should show different content based on login. I do not want to require javascript for login/logout.
I would like the solution to use tokens as I have not used session state anywhere else, but I am open to other options providing they would scale horizontally on a cloud provider (users next request may go to a different instance of back-end).
Anyone have example of an ideal solution?
(N.B: I am not interested in an externally hosted service).
ServiceStack's Authentication can also be used by external ASP.NET Web Frameworks, the ServiceStack and MVC Integration docs shows how you can accept Login credentials from a MVC Controller and register them with ServiceStack:
public ActionResult Login(string userName, string password, string redirect=null)
{
if (ModelState.IsValid)
{
try
{
using (var authService = ResolveService<AuthenticateService>())
{
var response = authService.Authenticate(new Authenticate {
provider = CredentialsAuthProvider.Name,
UserName = userName,
Password = password,
RememberMe = true,
});
// add ASP.NET auth cookie
FormsAuthentication.SetAuthCookie(userName, true);
return Redirect(string.IsNullOrEmpty(redirect) ? "/" : redirect);
}
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
}
}
return View("Index", GetViewModel());
}
The http://mvc.servicestack.net Live Demo shows an example of calling this controller using a standard MVC HTML Form.
Your MVC Controllers can then inherit ServiceStackController to access the Authenticated Users Session and different ServiceStack providers, here are the API's relating to Session and Authentication:
public class ServiceStackController : Controller
{
//...
ISession SessionBag { get; set; }
bool IsAuthenticated { get; set; }
IAuthSession GetSession(bool reload = true);
TUserSession SessionAs<TUserSession>();
void ClearSession();
}
Enable OAuth Providers
Should you need to you can also enable ServiceStack's different OAuth providers which can optionally callback either directly to a ServiceStack Service or
Further to mythz answer I also needed to know if a user was authenticated in a view and the normal Request.IsAuthenticated does not work when your doing above. So I created a CustomWebViewPage (to use this you will have to change *pageBaseType="Your.NameSpace.CustomWebViewPage" in the View folder's Web.config).
public abstract class CustomWebViewPage : WebViewPage
{
private IServiceStackProvider _serviceStackProvider;
public virtual IServiceStackProvider ServiceStackProvider
{
get
{
return _serviceStackProvider ?? (_serviceStackProvider =
new ServiceStackProvider(new AspNetRequest(base.Context, GetType().Name)));
}
}
public virtual bool IsAuthenticated
{
get { return ServiceStackProvider.IsAuthenticated; }
}
}
public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel>
{
//EXACTLY the same as above method...
Related
I'm developing an asp.net MVC website with the following requirements:
Develop pages for Admin and Users, these pages must be accessed
based on logged in user role: Admin or User
The website supports login only, You will call a PHP API which resides on an external website, it returns a JSON as a result that includes id, username, and role (admin, user)
You may save the result of returned json on a session to be used in your pages but this data must disappear after logout or session expiration.
I know how to develop the calling HTTP stuff and processing json, but I'm not familiar with authorization and authentication stuff, nor with using membership providers, I searched a lot and at first I thought of using SimpleMembership but I found that won't work since it depends on SQL queries and in my case I'm not going to use any type of databases.
I heard about asp.net identity but I'm not sure how to use it or if it's for my case or not, I searched again and I couldn't find any resource to help me achieve authentication and authorization for my case
I'm asking for your help to help me out and point me in the right direction
Thank you for your help
There is an example of using OAuth separated http auth API:
http://www.asp.net/web-api/overview/security/external-authentication-services
Yes, this example depends on some specified http API..
But in case when you have some another JSON/XML RPC API you can try to create your own feature like a:
public class ExternalAuthAPIClient {
public User Auth(string username, string password) { .... }
}
And use it in your AuthController in the method Login
BUT! This approach requires a lot of side changes.. where to store your user.. then create custom AuthenticateAttribure ... etc.
The better solution is to create oAuth supported API on your PHP side and use it with ASP.NET Identity.
I finally found a solution,I didn't need to use any membership providers since my website supports only login and via an API,I wrote the following code,this one is in AccountController :
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel login, string returnUrl)
{
if (!ModelState.IsValid)
{
ViewBag.Error = "Form is not valid; please review and try again.";
return View(login);
}
//Call external API,check if credentials are valid,set user role into userData
string userData="Admin";
var ticket = new FormsAuthenticationTicket(
version: 1,
name: login.Username,
issueDate: DateTime.Now,
expiration: DateTime.Now.AddSeconds(HttpContext.Session.Timeout),
isPersistent: false,
userData: userData);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Response.Cookies.Add(cookie);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", userData);
}
Then decorate admin/user controller with Authorize attribute like this:
[Authorize(Roles = "admin")]
public class AdminController : Controller
Then add the following code in Global.asax :
public override void Init()
{
base.PostAuthenticateRequest += Application_PostAuthenticateRequest;
}
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var decodedTicket = FormsAuthentication.Decrypt(cookie.Value);
var roles = decodedTicket.UserData;
var principal = new GenericPrincipal(HttpContext.Current.User.Identity, roles);
HttpContext.Current.User = principal;
}
}
I may be missing something obvious here. I'm new to both MVC and Web API, so I'm working on keeping my head above water.
I have an MVC application that interfaces with a Web API service. Authentication will be handled by a login service developed internally. When working, the MVC client should check if the current user is authenticated. If they're not, then it will redirect to this login service, which is supposed to authenticate the user and update the current user. I then need to be able to access this identity from the Web API service.
I'm operating under the assumption that the current principal (set via Thread.CurrentPrincipal or HTTPContext.Current.User) in the MVC application should be available in my Web API service, but whenever I try to access it from the service, the principal is empty. I've tried accessing the principal from the service using all of the following options, but it's always empty:
RequestContext.Principal
User.Identity
HttpContext.Current.User
Thread.CurrentPrincipal
Here's the basic idea of my code:
MVC Controller:
public ActionResult Index() {
//Just create a test principal here to see if it's available in the service
IPrincipal temp = new GenericPrincipal(new GenericIdentity("myUserName"), new string[]{});
Thread.CurrentPrincipal = temp;
using (var client = new HttpClient()) {
client.BaseAddress = new Uri("myServiceAddress");
HttpResponseMessage response = client.GetAsync("resourceString")).Result;
...Code to deal with result
}
}
Web API Controller:
[HttpGet]
public HttpResponseMessage MyAction() {
if (User.Identity == null || !User.Identity.IsAuthenticated) {
//So sad
} else {
//Do some work
}
}
The current principal is always empty, regardless of how I try to access it.
I think that you're going to need to set both the thread and context principal. Here's what I'm doing:
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) {
HttpContext.Current.User = principal;
}
}
Part way down This Article it says:
If your application performs any custom authentication logic, you must set the principal on two places:
Thread.CurrentPrincipal. This property is the standard way to set the thread's principal in .NET.
HttpContext.Current.User. This property is specific to ASP.NET.
I have a MVC4 web application set up, which uses Forms authentication and Web API for interaction. All API controllers use the [Authorize] attribute.
This was working just fine out of the box, until we started adding role support. Instead of implementing a full-fledged RoleProvider, I added a list of roles to the ticket's UserData, and created the following module:
public class SecurityModule : IHttpModule
{
public void Init(HttpApplication context)
{
var roleManager = (RoleManagerModule)context.Modules["RoleManager"];
roleManager.GetRoles += GetRoles;
}
void GetRoles(object sender, RoleManagerEventArgs e)
{
var user = e.Context.User;
if (user.Identity.IsAuthenticated && !(user is MyCustomPrincipal))
{
var roles = GetRolesFromFormsAuthCookie();
if (roles != null)
e.Context.User = new MyCustomPrincipal(user.Identity, roles,
otherData);
}
e.RolesPopulated = true;
}
}
This works flawlessly for MVC calls. For API, however, even though GetRoles gets called, when it reaches the corresponding method, it's back to GenericPrincipal.
How can I make this work with Web API too? Do I have to create a DelegatingHandler?
I'm also storing some custom data in my Principal, which might be a reason not to rely on just a RoleProvider (since I'd end up with a RolePrincipal), although I could just store that in the request context.
Update: I've now added a DelegatingHandler that does the same as the IHttpModule and sets Thread.CurrentPrincipal. Is this a reasonable approach?
Have you tried to set the Thread.CurrentPrincipal in the HttpModule as well ?. You can also use a Katana handler, which will work for both, ASP.NET MVC and ASP.NET Web API.
I have made asp.net mvc application that have custom forms authentication.
Beside that it needs to authenticate user from sharepoint (in other words I need to pass user from sharepoint to asp mvc application). SP and asp mvc app are in the same domain and SP is using AD to authenticate user. I have searched google/so and so far I haven`t got any good solution.
Note: I need secure way of passing user from sp to asp mvc application ... I saw few examples that pass user thought URL parameter and I think that this is not secure thing to do.
Why not to use url paramenter?
public class SecureToken {
public Int32 UserId {get;set;}
public DateTime DateCreated {get;set;}
public DateTime ValidTill {get;set;}
public SecureToken (Int32 userId) {
this.UserId = userId;
this.DateCreated = DateTime.Now;
this.ValidTill = this.DateCreated.AddMinutes(0.5);
}
public String ToEncryptedToken() {
// Do serialization,
// Then encrypt with, for example TrippleDES
// Escape for url
// return the string arguement for url
}
public static SecureToken Decrypt(String input) {
// If the DateCreated == ValidTill - 30 seconds
// If validTill > Now
// If decryptable
// Return deserialized token
// else throw Authentication error.
}
}
The point here is that the token while in URL is viable only for 30 seconds.
As an additional parameter you can use HMAC-SHA 256 during serialization and check weather this is really your token.
You could configure SP for a custom forms auth provider which in turn validates to the domain - then you are sharing forms auth tokens between apps which is fairly easy:
http://msdn.microsoft.com/en-us/library/ie/eb0zx8fc.aspx
after a lot of googling and reading several solutions on how to manage mixed mode authentication in ASP.NET apps, I still have no fitting solution for my problem.
I've got to implement an intranet application for a bunch of different user groups. Until now i've used windows authenthication which was very simple to implement. My problems arise when it comes to authorizing usergroups for special application functionalities.
Using [Authorize(Users = "DOMAIN\\USER")] works great but due to that i have no access to the active directory managament, it is impossible to me to configure rolemanagement in the way I need it for my application.
What I'd like to do is defining custom roles and memberships in addition to the ones that are defined within the active directory (is such an extension possible? e.g. by implementing an own membershipprovider?).
What do you think is the best solution for my problem. Do I really have to implement a complex mixed mode authentication with forms authentication in addition to windows authentication?
Used Technologies:
MS SQL Server 2008
MS VS 2010
ASP.NET MVC 3 - Razor View Engine
Telerik Extensions for ASP.NET MVC
IIS 7 on Windows Server 2008
EDIT (final solution thanks to the help of dougajmcdonald):
After pointing me to use a custom IPrincipal implementation I've found some solutions here and here. Putting everything together I came to the following solution:
1.Create a custom principal implementation:
public class MyPrincipal: WindowsPrincipal
{
List<string> _roles;
public MyPrincipal(WindowsIdentity identity) : base(identity) {
// fill roles with a sample string just to test if it works
_roles = new List<string>{"someTestRole"};
// TODO: Get roles for the identity out of a custom DB table
}
public override bool IsInRole(string role)
{
if (base.IsInRole(role) || _roles.Contains(role))
{
return true;
}
else
{
return false;
}
}
}
2.Integrate my custom principal implementation into the application through extending the "Global.asax.cs" file:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
WindowsIdentity wi = (WindowsIdentity)HttpContext.Current.User.Identity;
MyPrincipal mp = new MyPrincipal(wi);
HttpContext.Current.User = mp;
}
}
3.Use my custom roles for authorization in my application
public class HomeController : Controller
{
[Authorize(Roles= "someTestRole")]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
}
It works!!! yeah!
I'm not sure if this still applies in MVC, but in Webforms one way to do this would be as follows:
Create a new IPrincipal implementation perhaps extending WindowsPrincipal
In this class, give it a collection of roles (your own custom roles)
Populate those roles, by perhaps getting them from the DB.
Override IsInRole to return true if the role provided is EITHER true from the base call (WindowsAuthentication/Role) OR from your own custom role collection.
This way you can still hook into Principal.IsInRole("MyRole") and also the principal [PrincipalPermission()] annotation.
Hope it helps.
EDIT in answer to q's:
To integrate the principal into the authorisation you need to write your own method for OnAuthenticate in the global.asax for the type of authentication, so I would guess for you, something like this:
void WindowsAuthentication_OnAuthenticate(object sender, WindowsAuthenticationEventArgs e)
{
// ensure we have a name and made it through authentication
if (e.Identity != null && e.Identity.IsAuthenticated)
{
//create your principal, pass in the identity so you know what permissions are tied to
MyCustomePrincipal opPrincipal = new MyCustomePrincipal(e.Identity);
//assign your principal to the HttpContext.Current.User, or perhaps Thread.Current
HttpContext.Current.User = opPrincipal;
}
}
I believe Authorize came in at a later date to the PrincipalPermission, but I'm not too sure as to when/why of the differences I'm afraid :( - sorry!