I'm having trouble solving architecture of an ASP MVC application that servers html pages and web services through ServiceStack.
The application lives in the base url eg "http://myapplication.com" and SS lives in "http://myapplication.com/api" because it is the easiest way to configure both.
In general everything works fine, but when I reached the part of the authorization and authentication, is where I'm stuck.
For one, I need the application handle cookies as ASP normally do FormsAuthentication through, and users would go through a login screen and could consume actions and controllers when the attribute "Authorize" is used. This is typical of ASP, so I have no problem with it, such as "http://myapplication.com/PurchaseOrders".
On the other hand, clients of my application will consume my web service api from javascript. Those web services will also be tagged in some cases with the attribute "Authenticate" of ServiceStack. For example "http://myapplication.com/api/purchaseorders/25" would have to validate if the user can view that particular purchase order, otherwise send a 401 Unauthorized so javascript can handle those cases and display the error message.
Last but not least, another group of users will make use of my API by a token, using any external application (probably Java or .NET). So I need to solve two types of authentication, one using username and password, the other by the token and make them persistant so once they are authenticated the first time, the next calls are faster to solve from the API.
This is the code that I have so far, I've put it very simply to make clear the example.
[HttpPost]
public ActionResult Logon(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
JsonServiceClient client = new JsonServiceClient("http://myapplication.com/api/");
var authRequest = new Auth { provider = CredentialsAuthProvider.Name, UserName = model.UserName, Password = model.Password, RememberMe = model.RememberMe };
try
{
var loginResponse = client.Send(authRequest);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(loginResponse.UserName, false, 60);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
Response.Cookies.Add(cookie);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Test");
}
}
catch (Exception)
{
ModelState.AddModelError("", "Invalid username or password");
}
}
return View();
}
As for the authentication provider I am using this class
public class MyCredentialsAuthProvider : CredentialsAuthProvider
{
public MyCredentialsAuthProvider(AppSettings appSettings)
: base(appSettings)
{
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
if (userName == "testuser" && password == "nevermind")
{
return true;
}
else
{
return false;
}
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
//Fill the IAuthSession with data which you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
session.CreatedAt = DateTime.Now;
session.DisplayName = "Mauricio Leyzaola";
session.Email = "mauricio.leyzaola@gmail.com";
session.FirstName = "Mauricio";
session.IsAuthenticated = true;
session.LastName = "Leyzaola";
session.UserName = "mauricio.leyzaola";
session.UserAuthName = session.UserName;
var roles = new List<string>();
roles.AddRange(new[] { "admin", "reader" });
session.Roles = roles;
session.UserAuthId = "uniqueid-from-database";
//base.OnAuthenticated(authService, session, tokens, authInfo);
authService.SaveSession(session, SessionExpiry);
}
}
On the Configure function of AppHost I am setting my custom authentication class to use it as the default. I guess I should create another class and add it here as well, to handle the token scenario.
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new MyCredentialsAuthProvider(appSettings)
}, htmlRedirect: "~/Account/Logon"));
So far, ServiceStack is working as expected. I can submit a post to /auth/credentials passing username and password and it stores this information, so next call to a service the request is already authorized, great so far!
The question I need to know is how to call (and probably set somewhere in SS) the user that is logging in from my Account controller. If you see the first block of code I am trying to call the web service (looks like I am doing it wrong) and it works, but the next call to any web service looks unauthenticated.
Please don't point me to ServiceStack tutorials, I've been there for the last two days and still cannot figure it out.
Thanks a lot in advance.
Here is what I usually use:
You can replace the "Logon" action method with the code below:
...and the plugins:
....the Auth provider classes:
...finally, the logging utility class