ServiceStack - Custom CredentialsAuthProvider within .Net MVC app - servicestack

I am attempting to authenticate against MVC and ServiceStack following the example here - https://github.com/ServiceStack/ServiceStack.UseCases/tree/master/CustomAuthenticationMvc.
My issue is that I am unable to authenticate successfully against ServiceStack on my initial request to Account/LogOn.
ServiceStack related code in LogOn method of AccountController:
var apiAuthService = AppHostBase.Resolve<AuthService>();
apiAuthService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var apiResponse = apiAuthService.Authenticate(new Auth
{
UserName = model.UserName,
Password = model.Password,
RememberMe = false
});
I have a custom Authentication Provider that subclasses CredentialsAuthProvider. I Configure as follows in the AppHost class:
var appSettings = new AppSettings();
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new ActiveDirectoryAuthProvider(),
}));
public override bool TryAuthenticate(ServiceStack.ServiceInterface.IServiceBase authService, string userName, string password)
{
//class to authenticate against ActiveDirectory
var adAuthentication = new ActiveDirectoryAuthenticationService();
if (!adAuthentication.Authenticate(userName, password))
return false;
var session = (CustomUserSession)authService.GetSession(false);
session.IsAuthenticated = true;
session.UserAuthId = session.UserAuthName;
authService.SaveSession(session, SessionExpiry);
return true;
}
I think my issue is that session.Id is null at this point and saving the session persists 'urn:iauthsession:' to the 'SessionCache'. However, I'm not sure how to correctly populate session.Id. Also, this may or may not be an issue, but the initial LogOn request is to Account/Logon which is handled by MVC. So, there is no request to ServiceStack prior to the AuthService.Authenticate() call in the AccountController.
A possible solution I came up with has been added below in my subclass of CredentialsAuthProvider.
public override bool TryAuthenticate(ServiceStack.ServiceInterface.IServiceBase authService, string userName, string password)
{
//class to authenticate against ActiveDirectory
var adAuthentication = new ActiveDirectoryAuthenticationService();
if (!adAuthentication.Authenticate(userName, password))
return false;
var session = (CustomUserSession)authService.GetSession(false);
//A possible solution???
if(session.Id == null)
{
var req = authService.RequestContext.Get<IHttpRequest>();
var sessId = HttpContext.Current.Response.ToResponse().CreateSessionIds(req);
session.Id = sessId;
req.SetItem(SessionFeature.SessionId, sessId);
}
//end possible solution
session.IsAuthenticated = true;
session.UserAuthId = session.UserAuthName;
authService.SaveSession(session, SessionExpiry);
return true;
}
Is there a configuration or call I'm missing to 'wire up' ServiceStack Authentication within MVC?
Thanks.

The only thing I am doing in my TryAuthenticate is validating the user name, password and returning true if valid.
I have another override method called OnAuthenticated where I am saving the session information. OnAuthenticated passes the Auth Service and the Session as parameter so you only have to:
public override void OnAuthenticated(IServiceBase authService, IAuthSession session,.....
{
session.IsAuthenticated = true;
session.....
authService.SaveSession(session, SessionExpiry);
}
This seems to store my session information as long as I registered the ICacheClient.

Related

Servicestack - Authentication questions

I am currently fighting a bit with my custom CredentialsAuthProvider implementation. First it is important to say, that I am writing a WPF client as a reference for my API.
A browser stores cookies and you can configure how to deal with them, e.g. delete when the browser is closed. On windows desktop you have Environment.SpecialFolder.Cookies where Windows stores cookies. But I could not find anything from ServiceStack. So does it not store anything on a Windows Desktop app? I saw there is a client.CookieContainer where I find three cookies after login.
Can I somehow add properties to this cookie during Authentication? If so how? Currently I use AuthenticationResponse.MetaDictionary to transfer additional information:
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
var authResponse = (AuthenticateResponse)base.Authenticate(authService, session, request);
authResponse.Meta = new Dictionary<string, string>();
authResponse.Meta.Add("Test", "TestValue");
return authResponse;
}
And finally: Is an instance of my derived CredentialsAuthProvider class thread safe? In TryAuthenticate(...) I make a DB connection and retrieve an object which contains all information including hashed password etc. But I can only fill this information to the session object in OnAuthenticated(....) and/or overridden Authenticate(...). If possible I do not want to make another DB call to retrieve the same object again. So is it safe to declare a member user fill it in TryAuthenticate and reuse it in other overwritten methods like so:
public class BediCredentialsAuthProvider : CredentialsAuthProvider
{
private AppUser user = null;
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
var authResponse = (AuthenticateResponse)base.Authenticate(authService, session, request);
authResponse.Meta = new Dictionary<string, string>();
authResponse.Meta.Add("ValueA", user.ValueA);
// ... add more properties from user object
return authResponse;
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
AppUser user = null;
using (var session = NhSessionFactories.OpenSession(TopinConstants.TopInDbFactory))
{
using (var transaction = session.BeginTransaction())
{
try
{
var appUserRepo = new AccountManagementRepository(session);
user = appUserRepo.GetAppUser(userName); // get user from database using NHibernate
transaction.Commit();
session.Close();
}
catch (Exception ex)
{
Log.Error($"Error retrieving user {user} to authenticate. Error: {ex}");
throw;
}
}
}
// do some logic to test passed credentials and return true or false
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
session.DisplayName = user.DisplayName;
session.FirstName = user.Firstname;
session.LastName = user.Lastname;
session.Email = user.EmailAddress;
// etc.....
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
}
You can populate ServiceStack Service Client Cookies just like you would a browser except it only retains permanent Session Ids where you'll need to authenticate with RememberMe=true, e.g:
var response = client.Post(new Authenticate {
provider = "credentials",
UserName = ...,
Password = ...,
RememberMe = true,
});
Which will save the Authenticated User Session against the ss-pid permanent Cookie in the HttpWebRequest CookieContainer and gets sent on every subsequent request.
You can set your own Permanent Cookies in OnAuthenticated from authService with:
var httpRes = authService.Request.Response;
httpRes.SetPermanentCookie(cookieName, cookieValue);
Is an instance of my derived CredentialsAuthProvider class thread safe?
No the same AuthProvider singleton instance is used to Authenticate each request so you can't maintain any stored variables on the instance itself and will need to remove:
//private AppUser user = null; //Instance variables are not ThreadSafe
If you want to pass items and access them throughout the Request Pipeline you can store them in IRequest.Items Dictionary, e.g:
authService.Request.Items["AppUser"] = user;

Sending custom parameter on authentication

I'm a servicestack newbie. I'm trying to figure out how to send custom parameter on authentication.
As far as I understood, that's the step to authenticate a client and than execute a set of call within a session
var jsonClient = new JsonServiceClient("http://localhost:55679/");
var authResponse = client.Send(new Authenticate
{
provider = "myProvider",
UserName = "user",
Password = "pwd",
RememberMe = true,
});
var jResponse = jsonClient.Get<CountriesResponse>(request);
Console.WriteLine(jResponse.Countries.Count);
So far so good, I configurated my apphost as following and everything works as expected.
Plugins.Add(new AuthFeature(() => new CustomUserSession(), new IAuthProvider[] {
new MyAuthProvider(),
}));
What should I do if, instead of sending ServiceStack.Authenticate, I'd like to send my MyAuthenticate
request that has same custom properties, somenthing like this?
var authResponse = client.Send(new MyAuthenticate
{
provider = "myProvider",
UserName = "user",
Password = "pwd",
RememberMe = true,
AppId = "AppId",
ProjectId = "ProjectId"
});
My goal is to send custom parameter while I'm authenticating the user, not just those allowed by Authenticate built-in request, and than store those extra parameter within my CustomUserSession.
Thanks
Sending additional info on QueryString or HttpHeaders
As you can't change the built-in Authenticate Request DTO, one way to send additional metadata is to add extra info on the QueryString or HTTP Headers.
If you wanted to use the .NET Service Clients to do this you would need to use the RequestFilter, e.g:
var client = new JsonServiceClient(BaseUrl) {
RequestFilter = req => {
req.QueryString["AppId"] = appId;
req.QueryString["ProjectId"] = appId;
}
};
var authResponse = client.Send(new Authenticate { ... });
Otherwise creating custom Request is often more flexible using ServiceStack's built-in HTTP Utils, e.g:
var url = "{0}/auth/myProvider".Fmt(BaseUrl)
.AddQueryParam("AppId", appId)
.AddQueryParam("ProjectId", projectId);
var authResponse = url.PostJsonToUrl(new Authenticate { ... });
On the server the additional data will be available in the QueryString of the current request which you can get from IServiceBase or IRequest args, e.g:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
...
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
...
var customSession = (CustomUserSession)session;
customSession.AppId = authService.Request.QueryString["AppId"];
customSession.ProjectId = authService.Request.QueryString["ProjectId"];
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
}
Custom Meta dictionary now available on Authenticate Request DTO
To make this use-case a little easier a new Dictionary<string,string> Meta property was added on the Authenticate DTO which makes calling from the Typed Service Clients a little nicer since you don't have to use a filter, e.g:
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate {
...
Meta = new Dictionary<string, string> { {"AppId", appId}, {"ProjectId", pId} },
});
Which you can access from the Authenticate DTO directly, e.g:
var authRequest = (Authenticate)authService.Request.Dto;
customSession.AppId = authRequest.Meta["AppId"];
customSession.ProjectId = authRequest.Meta["ProjectId"];
The new Meta property is available from v4.0.35+ that's currently available on MyGet.
Use your own Custom Authentication Service
A more disruptive alternative approach to be able to use your own MyAuthenticate DTO is to handle the authentication request in your own Service and then delegate to the AuthService, e.g:
public class MyAuthenticate : Authenticate
{
public string AppId { get; set; }
public string ProjectId { get; set; }
}
public class MyAuthServices : Service
{
public object Any(MyAuthenticate request)
{
using (var auth = base.ResolveService<AuthenticateService>())
{
var response = auth.Post(request);
var authResponse = response as AuthenticateResponse;
if (authResponse != null) {
var session = base.SessionAs<CustomUserSession>();
session.AppId = request.AppId;
session.ProjectId = request.ProjectId;
this.SaveSession(session);
}
return response;
}
}
}

Call ServiceStack API Programmatically

What are the best ways to call a servicestack API from asp.net website. Service is running in IIS.
All the methods inside the service stack require authentication first.
I tried with JsonServiceClient and HttpWebRequest. First time when I authenticate, service gives me cookies
ss-id and ss-pid and I store in cookies collection. Now when I request another method It says, You are not authorised.
The problem is, in the second request Cookies are not maintained. However if you test the service from browser it self. It do create cookies first during Authorization and in second request, It gives you proper response.
Below is my code. With JsonServiceClient and HttpWebRequest
[HttpPost]
public ActionResult Index(Login loginModel)
{
#region ServiceStack Call
HttpCookie ck;
string baseUrl = "http://192.168.1.101";
var client = new JsonServiceClient(baseUrl);
var authResponse = client.Send<AuthenticateResponse>(new Authenticate
{
UserName = loginModel.UserName,
Password = loginModel.Password,
RememberMe = true
});
foreach (System.Net.Cookie cookie in client.CookieContainer.GetCookies(new Uri(baseUrl)))
{
if (cookie.Name == "ss-id")
{
ck = new HttpCookie(cookie.Name);
ck.Value = cookie.Value;
ck.Expires.AddDays(1); //Check when to expire the cookie
Response.Cookies.Add(ck);
}
}
}
Below Code with HttpWebRequest
protected string CallToApi(string baseUrl, bool createCookie)
{
CookieContainer cc = new CookieContainer();
System.Uri uri = new Uri(baseUrl);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.ContentType = #"application/json; charset=utf-8";
request.Timeout = 200000;
request.Accept = "application/json";
request.CookieContainer = new CookieContainer();
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(responseStream, System.Text.Encoding.UTF8);
if (reader != null)
{
if (createCookie)
{
//Create Cookies
}
}
return reader.ReadToEnd();
}
}
catch (WebException ex)
{
throw ex;
}
}
How to give call to second method?
http://192.168.1.101/api/teamleaders URL
When calling this Url, Can I persist my cookies? or there must be out of the box in ServiceStack itself.
The JsonServiceClient should persist cookies. I've used the following code (with the default CredentialsAuthProvider) successfully:
var client = new JsonServiceClient(baseUri);
var authResponse = client.Post(new Auth
{
provider = CredentialsAuthProvider.Name,
UserName = "username",
Password = "password",
RememberMe = true
});
Note: this is with version 3.9.71, NOT the new v4 stack which I haven't yet had the opportunity to upgrade to. The same 'should' work with a custom auth provider inheriting from CredentialsAuthProvider.

MVC5 (VS2012) UserManager doesn't sign user in

This is a continuation of this question.
If I override the userManager:
public class NHibernateAspnetUserManager<TUser> : UserManager<TUser> where TUser : IdentityUser
{
public NHibernateAspnetUserManager(IUserStore<TUser> store) : base(store)
{
}
public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
{
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
return Task.FromResult(identity);
}
}
This doesn't throw any errors but will not log the user in, (log process happens but #Request.IsAuthenticated will always return false). If I don't override it then I get a "System.Security.Claims.Claim..ctor" error as described in the other question. To try and solve that my own userstore implemented IUserClaimStore but simply return a new list of claims.
I am not sure what the default usermanager does under the hood that differs. I am guessing it sets up some form of claim identity object that allows MVC to recognise someone as logged in.
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent}, identity);
EDIT
Found out why the ctor error was occuring. The user object was coming back without the ID so the default UserManager was getting upset. Fixed that and used the default UserManager which now no longer throws an error, but still doesn't log the user in. The identity object it returns looks good from what I can tell.
FURTHER NOTE
So I installed VS2013 and copied the store and NHibernate repo across, all worked first time. I can only assume there is some sutble difference between creating it and updating MVC5 in VS2012 and doing it in VS2013.
So the main issue is that you aren't respecting the authentication type in your method, you need to create a ClaimsIdentity for DefaultAuthenticationType.ApplicationCookie, here's what the default claims factory does:
public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
{
var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
id.AddClaim(new Claim(UserIdClaimType, ConvertIdToString(user.Id), ClaimValueTypes.String));
id.AddClaim(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String));
I've faced the same problem implementing custom identity using ASP.NET 4.5. And the problem really was in adding null values into the Claims collection (see the comments):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new AppUser { UserName = model.UserName };
// after the UserManager creates a user, all the properties of
// AppUser except "UserName" are automatically discarded
var result = await UserManager.CreateAsync(new AppUser
{
UserRealName = model.UserRealName,
UserName = model.UserName,
Password = model.Password
}, model.Password);
if (result.Succeeded)
{
// So we need to re-get the new user
user = AppUser.GetByName(model.UserName);
await SignInAsync(user, false); // otherwise here we will add null values ...
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
return View(model);
}
private async Task SignInAsync(AppUser user, Boolean isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
var identity = await UserManager.CreateIdentityAsync(user,
DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties // ... into the list of
// claims for all AppUser properties except UserName
{ IsPersistent = isPersistent }, identity);
}

Return a custom auth response object from ServiceStack authentication

Is it possible to return a custom auth response? I already have my own custom authentication provider that inherits from CredentialsAuthProvider.
I want to return the session expiry date in the response, so that the client knows exactly when their server session will expire:
{
"sessionId": "bG27SdxbRkqJqU6xv/gvBw==",
"userName": "joe.bloggs#letmein.com",
"sessionExpires": "2013-04-29T03:27:14.0000000",
"responseStatus": {}
}
I can override the Authenticate method like so:
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
// get base response
var response = base.Authenticate(authService, session, request);
// grab the session
var customSession = authService.GetSession() as CustomUserSession;
// if response can be cast and customSession exists
if (response is AuthResponse && customSession != null)
{
// cast
var authResponse = response as AuthResponse;
// build custom response
var customAuthResponse = new CustomAuthResponse
{
ReferrerUrl = authResponse.ReferrerUrl,
SessionExpiry = customSession.SessionExpires,
SessionId = authResponse.SessionId,
ResponseStatus = authResponse.ResponseStatus,
UserName = authResponse.UserName
};
return customAuthResponse;
}
// return the standard response
return response;
}
This works fine, except in the case where the session already is active. In that case, the AuthService Post method checks for a valid session and automatically returns a standard AuthResponse, and there is no obvious way to override it:
var alreadyAuthenticated = response == null;
response = response ?? new AuthResponse {
UserName = session.UserAuthName,
SessionId = session.Id,
ReferrerUrl = referrerUrl,
};
Following Paaschpa's ideas below, the following forces re-auth to always be re-authenticated, but it seems like there could be risks involved in leaving multiple active sessions open:
public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
{
// force re-authentication. Not great, but no other obvious way to do this
if (request != null)
{
return false; // auth or re-auth calls
}
return base.IsAuthorized(session, tokens, request);
}
Can anyone think of a better way to do this? I could implement my own AuthenticationService, but I'm not sure how I would override the AuthFeature?
If I understand correctly, you want to return a custom response after a user authenticates against '/auth/credentials'. Since you already have your own CredentialsAuthProvider I think you could just override Authenticate and return your own response.
Subclass of CredentialsAuthProvider
public class MyCredentialsAuthProvider : CredentialsAuthProvider
{
public override object Authenticate(ServiceStack.ServiceInterface.IServiceBase authService, IAuthSession session, Auth request)
{
//let normal authentication happen
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
//return your own class, but take neccessary data from AuthResponse
return new
{
UserName = authResponse.UserName,
SessionId = authResponse.SessionId,
ReferrerUrl = authResponse.ReferrerUrl,
SessionExpires = DateTime.Now
};
}
}

Resources