Customising ServiceStack Authentication - servicestack

I have read the documentation and have successfully implemented a custom authentication layer like below:
public class SmartLaneAuthentication : CredentialsAuthProvider
{
private readonly SmartDBEntities _dbEntities;
public SmartLaneAuthentication(SmartDBEntities dbEntities)
{
_dbEntities = dbEntities;
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var user = _dbEntities.Users.FirstOrDefault(x => !((bool)x.ActiveDirectoryAccount) && x.UserName == userName);
if (user == null) return false;
// Do my encryption, code taken out for simplicity
return password == user.Password;
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
// user should never be null as it's already been authenticated
var user = _dbEntities.Users.First(x => x.UserName == session.UserAuthName);
var customerCount = _dbEntities.Customers.Count();
session.UserName = user.UserName;
session.DisplayName = user.DisplayName;
session.CustomerCount = customerCount; // this isn't accessible?
authService.SaveSession(session, SessionExpiry);
}
}
I then register it in AppHost:
Plugins.Add(new AuthFeature(() => new SmartLaneUserSession(),
new IAuthProvider[]
{
new SmartLaneAuthentication(connection)
})
{
HtmlRedirect = null
});
Plugins.Add(new SessionFeature());
Notice I'm using a SmartLaneUserSession like below, where I have added a Custom Property called CustomerCount:
public class SmartLaneUserSession : AuthUserSession
{
public int CustomerCount { get; set; }
}
When I try and access this property to set it in the OnAuthenticated method of my SmartLaneAuthentication class, it isn't accessible. How would I access and set this property when the user is logged in?

In the OnAuthenticated method you will need to cast the session (of type IAuthSession) into your session object type, such as:
...
var customerCount = _dbEntities.Customers.Count();
var smartLaneUserSession = session as SmartLaneUserSession;
if(smartLaneUserSession != null)
{
smartLaneUserSession.UserName = user.UserName;
smartLaneUserSession.DisplayName = user.DisplayName;
smartLaneUserSession.CustomerCount = customerCount; // Now accessible
// Save the smartLaneUserSession object
authService.SaveSession(smartLaneUserSession, SessionExpiry);
}
In your service you can access the session using the SessionAs<T> method. So in your case you can use:
public class MyService : Service
{
public int Get(TestRequest request)
{
var session = SessionAs<SmartLaneUserSession>();
return session.CustomerCount;
}
}

Related

Using CustomCredentialsAuthProvider in JsonServiceClient

I try to implement my own custom CredentialsAuthProvider. The server seems to work fine with the following implementation:
public class MyCustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
if (userName == "testuser" && password == "1234")
{
return true;
}
else
{
return false;
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
session.FirstName = "Testuser Joe Doe";
authService.SaveSession(session, SessionExpiry);
return null;
}
}
When I call on my Browser http://localhost:8088/auth/credentials?UserName=testuser&Password=1234 I get back a page containing a session ID and the testuser Joe Doe. Looks fine.
Now I try to call this from my Windows WPF client. I have created a Login Page and a LoginViewModel class since I implement the MVVM pattern. But I do not understand, what I really have to set the provider property in the Authenticate class to.
In my WPF class I have the following:
public partial class App : Application
{
public JsonServiceClient ServiceClient { get; private set; }
public App()
{
this.InitializeComponent();
}
// ....
}
And then in my LoginViewModel I have a Login() method which is a RelayCommand implementation of the login button like so (The form contains also a field where you have to enter the name of the application server since there is more than one. This is why I compose the baseUri in the handler):
private void Login()
{
var baseUri = $"http://{AppServer}:8088";
((App)Application.Current).InitServiceClient(baseUri);
var client = ((App) Application.Current).ServiceClient;
//var response = client.Send<AuthResponse>(new Auth { UserName = "Test", Password = "TestPassword" });
var authResponse = client.Post(new Authenticate
{
provider = CredentialsAuthProvider.Name, // <-- WHAT SHOULD THIS BE???
UserName = "testuser",
Password = "1234",
RememberMe = true,
});
// ....
}
CredentialsAuthProvider is unknown by the compiler. What do I need to pass here and what assemblies do I need? So far I have:
ServiceStack.Ckient
ServiceStack.Interfaces
ServiceStack.Text
MyService.ServiceModel //DLL containing the DTOs etc., NO implementations
What am I missing and doing wrong here?
CredentialsAuthProvider.Name just provides typed access to the "credentials" string literal, which you can use in its place, e.g:
var authResponse = client.Post(new Authenticate
{
provider = "credentials",
UserName = "testuser",
Password = "1234",
RememberMe = true,
});
You can find the list of Auth provider literals in the Authentication docs.

AuthUserSession is null inside ServiceStack service after successful auth

I have a self hosted service stack app in which I'm using Facebook Oath and Redis - the Facebook and redis side of things seem to be working ie. when I visit
abc.com/auth/facebook
The custom user session gets populated in OnAuthenticated method. The Redis cache has the data persisted correctly..so far so good
The problem Im having is understanding how to retrieve this CustomUserSession in a subsequent request. To begin with the oauth redirect page "/About-Us" is where I want to retrieve the session value however it is always null
[DataContract]
public class CustomUserSession : AuthUserSession
{
[DataMember]
public string CustomId { get; set; }
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
// receiving session id here and can retrieve from redis cache
}
public override bool IsAuthorized(string provider)
{
// when using the [Authenticate] attribute - this Id is always
// a fresh value and so doesn't exist in cache and cannot be auth'd
string sessionKey = SessionFeature.GetSessionKey(this.Id);
cacheClient = ServiceStackHost.Instance.TryResolve<ICacheClient>();
CustomUserSession session = cacheClient.Get<CustomUserSession>(sessionKey);
if (session == null)
{
return false;
}
return session.IsAuthenticated;
}
}
[DefaultView("AboutUs")]
public class AboutUsService : AppServiceBase
{
public object Get(AboutUsRequest request)
{
var sess = base.UserSession;
return new AboutUsResponse
{
//custom Id is always null??
Name = sess.CustomId
};
}
}
public abstract class AppServiceBase : Service
{
protected CustomUserSession UserSession
{
get
{
return base.SessionAs<CustomUserSession>();
}
}
}
How I register the cache & session etc.
AppConfig = new AppConfig(appSettings);
container.Register(AppConfig);
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("10.1.1.10:6379"));
container.Register(c =>
c.Resolve<IRedisClientsManager>().GetCacheClient());
ConfigureAuth(container, appSettings);
the contents of ConfigureAuth()
var authFeature = new AuthFeature(
() => new CustomUserSession(),
new IAuthProvider[]
{
new FacebookAuthProvider(appSettings), // override of BasicAuthProvider
}
) {HtmlRedirect = null, IncludeAssignRoleServices = false};
Plugins.Add(authFeature);
I feel I'm missing something obvious here.... thanks in advance
To register to use a CustomUserSession for your typed Sessions, it needs to be specified when you register the AuthFeature, e.g:
Plugins.Add(new AuthFeature(() => new CustomUserSession(), ...));

Custom Table names in Identity 2.0, User.IsInRole() not working

I tried to rename the tables with the following code:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
modelBuilder.Entity<ApplicationUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
}
It worked. The table names have been renamed properly, and data seems to be getting inserted in the proper places.
However, The User.IsInRole("rolestring") method doesn't work. It returns false all the time.
If I remove the above code, everything works fine.
What am I missing?
Update:
On your Startup class, when configure the OAuthAuthorizationServerOptions, on Provider property you should have a custom class that inherits from OAuthAuthorizationServerProvider. In the example below, the CustomAuthorizationServerProvider class:
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new CustomAuthorizationServerProvider()
};
And this is code of CustomAuthorizationServerProvider, where you have to override GrantResourceOwnerCredentials:
public class CustomAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
...
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
IdentityUser user;
using (AuthRepository repository = new AuthRepository())
{
user = await repository.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect");
return;
}
}
UserManager<IdentityUser> userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(new AuthDBContext()));
ClaimsIdentity identity = await userManager.CreateIdentityAsync(user, context.Options.AuthenticationType);
AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", context.ClientId ?? string.Empty
},
{
"userName", context.UserName
},
{
"roles",String.Join(",", (IEnumerable<IdentityUserRole>) user.Roles.ToArray())
}
});
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
context.Validated(ticket);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
}
Note: AuthDBContext is the same class you are declaring your OnModelCreating method.
So, reviewing the code above, you'll check that user roles are inserted in AuthenticationProperties dictionary
{
"roles",String.Join(",", (IEnumerable<IdentityUserRole>) user.Roles.ToArray())
}
and then they are inserted in the ticket with the ClaimsIdentity object of the current user.
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
Once you've fixed this, you only have to add the [Authorize] attribute in your actions and/or controllers like this:
[Authorize(Roles = "Admin")]
Or check in your controller actions the equivalent:
ActionContext.RequestContext.Principal.IsInRole("Admin")

ServiceStack RequiredRole is not asking for role to access

I'm trying to define a permissions for a ServiceStack Service which only can access the Admin Role for example and I have this Service with the RequireRole attribute but it seems does not work because I can access the service as a USER .
[Authenticate]
[RequiredRole("Admin")]
public class HelloService : Service
{
public const string HelloServiceCounterKey = "HelloServiceCounter";
public object Any(HelloRequest request)
{
var userSession = SessionAs<AppHost.CustomUserSession>();
Session.Set(HelloServiceCounterKey, Session.Get<int>(HelloServiceCounterKey) + 1);
var roles = string.Join(", ", userSession.Roles.ToArray());
return new HelloResponse { Result = "Hello, " + request.Name + ", your role(s): " + roles };
}
}
AccountController.cs
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
if (!WebSecurity.UserExists("Admin"))
WebSecurity.CreateUserAndAccount("admin", "abc");
var authService = AppHostBase.Resolve<AuthService>();
authService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var response = authService.Authenticate(new Auth
{
UserName = model.UserName,
Password = model.Password,
RememberMe = model.RememberMe
});
// add ASP.NET auth cookie
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToLocal(returnUrl);
}
catch (HttpError)
{
}
}
and Here's my AppHost.cs
public override void Configure(Funq.Container container)
{
/*Register storage for User Session */
container.Register<ICacheClient>(new MemoryCacheClient()); /*Tipo Base de MemoryCacheClient es ICacheClient*/
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>())); /*Tipo Base de SessionFactory es ISessionFactory*/
Plugins.Add(new AuthFeature(
() => new CustomUserSession(),
new[] { new CustomCredentialsAuthProvider() }
));
Plugins.Add(new SessionFeature());
Routes
.Add<HelloService>("/hello")
.Add<HelloService>("/hello/{Name*}");
//Set JSON web services to return idiomatic JSON camelCase properties
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
container.Register(new TodoRepository());
//Set MVC to use the same Funq IOC as ServiceStack
ControllerBuilder.Current.SetControllerFactory(new FunqControllerFactory(container));
}
The wiki states:
As with Authenticate, you can mark services (instead of DTO) with
RequiredPermission attribute, too.
It does NOT state whether you can use the RequiredRole attribute with a service, so I think you cannot and looking at the comments in source it does seem to target just requestDTO object.

ServiceStack and FacebookAuthProvider

I've been working with ServiceStack and it's Auth providers. Specifically "FacebookAuthProvider".
My issue here is that the service is called from an iOS app. This app already have a valid access token and i just want to pass this value to servicestack facebook authentication.
I've seen the tests on servicestack github page, but it still doesn't make sense to me.
Is it possible to pass this access token to servicestack, so the authentication skips the part where i ask for permission, since we already did the on the app?
Or am i approching this the wrong way?
Instead of using the builtin facebook auth provider i created my own CustomFacebookAuthProvider.
The reason is that the builtin version needs a browser to redirect the user to facebook for authentication and i didn't need that. I already had an access token.
So based on the official version FacebookAuthProvider.cs i created my own.
using System;
using System.Collections.Generic;
using System.Net;
using Elmah;
using Mondohunter.Backend.BusinessLogic.Interfaces;
using ServiceStack.Common.Extensions;
using ServiceStack.Common.Web;
using ServiceStack.Configuration;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
namespace Mondohunter.Interfaces
{
public class CustomFacebookAuthProvider : OAuthProvider
{
public const string Name = "facebook";
public static string Realm = "https://graph.facebook.com/";
public static string PreAuthUrl = "https://www.facebook.com/dialog/oauth";
public string AppId { get; set; }
public string AppSecret { get; set; }
public string[] Permissions { get; set; }
public CustomFacebookAuthProvider(IResourceManager appSettings)
: base(appSettings, Realm, Name, "AppId", "AppSecret")
{
this.AppId = appSettings.GetString("oauth.facebook.AppId");
this.AppSecret = appSettings.GetString("oauth.facebook.AppSecret");
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var tokens = Init(authService, ref session, request);
try
{
if (request.oauth_token.IsNullOrEmpty())
throw new Exception();
tokens.AccessToken = request.oauth_token;
session.IsAuthenticated = true;
var json = AuthHttpGateway.DownloadFacebookUserInfo(request.oauth_token);
var authInfo = JsonSerializer.DeserializeFromString<Dictionary<string, string>>(json);
//Here i need to update/set userauth id to the email
//UpdateUserAuthId(session, authInfo["email"]);
authService.SaveSession(session, SessionExpiry);
OnAuthenticated(authService, session, tokens, authInfo);
//return json/xml/... response;
}
catch (WebException ex)
{
//return json/xml/... response;
}
catch (Exception ex)
{
//return json/xml/... response;
}
}
protected override void LoadUserAuthInfo(AuthUserSession userSession, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
if (authInfo.ContainsKey("id"))
tokens.UserId = authInfo.GetValueOrDefault("id");
if (authInfo.ContainsKey("name"))
tokens.DisplayName = authInfo.GetValueOrDefault("name");
if (authInfo.ContainsKey("first_name"))
tokens.FirstName = authInfo.GetValueOrDefault("first_name");
if (authInfo.ContainsKey("last_name"))
tokens.LastName = authInfo.GetValueOrDefault("last_name");
if (authInfo.ContainsKey("email"))
tokens.Email = authInfo.GetValueOrDefault("email");
if (authInfo.ContainsKey("gender"))
tokens.Gender = authInfo.GetValueOrDefault("gender");
if (authInfo.ContainsKey("timezone"))
tokens.TimeZone = authInfo.GetValueOrDefault("timezone");
LoadUserOAuthProvider(userSession, tokens);
}
public override void LoadUserOAuthProvider(IAuthSession authSession, IOAuthTokens tokens)
{
var userSession = authSession as CustomUserSession;
if (userSession == null) return;
userSession.Email = tokens.Email ?? userSession.PrimaryEmail ?? userSession.Email;
}
}
}
I hope it makes sense.

Resources