We have a ServiceStack host, in which we have modularised the services. In addition we have a custom authentication solution based on the Basic Authentication. But what we would like to do is have different authentication methods for different services, maybe based on routes? Is this possible?
Secondly, is it possible to assign a common route prefix based on the service? As I said we have modularised our services, and in the AppHost definition we enter the assemblies of the different services, but is it possible to change the route prefix, i.e. Service1 to localhost/api1/servicemethods, Service2 to localhost/api2/servicemethods etc.?
You can limit that a Service should only authenticate with a specific provider by specifying the provider name in the [Authenticate] attribute, e.g:
[Authenticate(AuthenticateService.ApiKeyProvider)]
public class ApiKeyAuthServices : Service
{
public object Any(ApiKeyOnly request) => ...;
}
[Authenticate(AuthenticateService.JwtProvider)]
public class JwtAuthServices : Service
{
public object Any(JwtOnly request) => ...;
}
Otherwise inside your Service you can inspect how the request was authenticated by looking at base.SessionAs<AuthUserSession>().AuthProvider.
For defining dynamic routes have a look at:
Auto Route Generation Strategies
Dynamically adding Route Attributes
Customizing Defined Routes
Although ServiceStack isn't designed to define different sets of Apps within the same AppHost so if that's what you're trying to do I'd recommend instead having different AppHosts and using the Service Gateway for any Service-to-Service communication.
Many thanks for your reply. I must be doing something fundamentally wrong, even though I have registered two custom authproviders, both based on the BasicAuthProvider, using AuthenticateService.GetAuthProviders() returns an empty array.
This is the code I use to register the AuthProviders, and they both allow me to login, so I know they are working.
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new RMCredentialsAuthProvider(),
new RMKOTAuthProvider()
}));
The code from one of the custom providers is
public class RMKOTAuthProvider : BasicAuthProvider
{
#region Public Constructors
public RMKOTAuthProvider() : base()
{
}
#endregion Public Constructors
#region Public Methods
public override Task<IHttpResult> OnAuthenticatedAsync(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo, CancellationToken token = default)
{
session.FirstName = session.UserAuthName;
session.Roles = new List<string>
{
"KOT"
};
authService.SaveSessionAsync(session, SessionExpiry);
return base.OnAuthenticatedAsync(authService, session, tokens, authInfo, token);
}
public override Task<bool> TryAuthenticateAsync(IServiceBase authService, string userName, string password, CancellationToken token = default)
{
try
{
if (userName.IsNullOrEmpty() || password.IsNullOrEmpty())
return Task.FromResult(false);
var result = VerifyUser(username, password);
return Task.FromResult(result);
}
catch (InvalidCastException)
{
return Task.FromResult(false);
}
}
#endregion Public Methods
}
Can you please explain what step I am missing such that GetAuthProviders() can list the providers, and I can use the metadata you described earlier.
Many thanks in advance for your help with this.
Related
Right now, we're authenticating our users with this:
public class WindowsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "OurDomain"))
{
// TODO make sure user record exists in custom DB tables as well
return pc.ValidateCredentials(userName, password);
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
}
Which works great when using the JsonServiceClient.
We have some legacy code written in Visual FoxPro which wants to call some of the authenticated functions in ServiceStack... to accommodate this, we'd like to also allow Api Keys. We want the API Keys to be stored in SQL Server to avoid issues if the process stops / restarts. So, the client would authenticate with domain credentials, then generate an API key for subsequent calls which would be stored in the database (ideally just using the table servicestack can create (dbo.ApiKey).
If we were to set this per the docs:
container.Register<IAuthRepository>(c => new OrmLiteAuthRepository(dbFactory));
We get an error on the OnAuthenticated function above telling us we should call Init()... like its trying to also create the user tables. So I'm not sure how to allow DB stored API Keys, along with custom authentication that relies on both active directory as well as our custom tables for users and roles.
Instead of inheriting from CredentialsAuthProvider, maybe its better to register a custom IUserAuthRepository and IManageRoles?
The API Key AuthProvider needs to be registered in your AuthFeature, e.g:
Plugins.Add(new AuthFeature(...,
new IAuthProvider[] {
new ApiKeyAuthProvider(AppSettings),
new WindowsAuthProvider(AppSettings),
//...
}));
Which requires a IAuthRepository like you're doing:
container.Register<IAuthRepository>(c =>
new OrmLiteAuthRepository(dbFactory));
Any AuthProvider that requires creating a back-end tables or other schema requires that its schema is initialized on Startup which you can do with:
container.Resolve<IAuthRepository>().InitSchema();
It's safe to always call InitSchema() as it only creates missing tables or is otherwise ignored for AuthRepositories that don't require creating a schema.
An issue you're running into is that you've registered an IAuthRepository and are inheriting a CredentialsAuthProvider which you don't want to use it in so you can't call CredentialsAuthProvider.OnAuthenticated() since it will save the User Auth info to the repository if it exists.
So you'll need to provide a custom implement without calling base.OnAuthenticated(), e.g:
public class WindowsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "OurDomain"))
{
// TODO make sure user record exists in custom DB tables as well
return pc.ValidateCredentials(userName, password);
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
try
{
session.IsAuthenticated = true;
session.OnAuthenticated(authService, session, tokens, authInfo);
AuthEvents.OnAuthenticated(authService.Request, session, authService, tokens, authInfo);
}
finally
{
this.SaveSession(authService, session, SessionExpiry);
}
return null;
}
}
Unsure how IMobileServiceSyncTable security works - say I have a table, and it stores data for multiple users.
Following this Azure App Services tutorial, it looks like I can query - from a mobile app - for pretty much any record, for any user, that I want.
Client-side (e.g., Xamarin):
await todoTable.PullAsync("todoItems" + userid,
syncTable.Where(u => u.UserId = userid));
Is there a way (server-side) to automatically scope records to the current authenticated user? Or is that done for you automatically if you decorate your table controllers with the [Authorize] attribute?
Server-side:
[Authorize]
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MyAppContext context = new MyAppContext();
DomainManager = new EntityDomainManager<TodoItem>(context, Request);
}
// GET tables/TodoItem
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
}
Check out this blog post (assuming ASP.NET): http://shellmonger.com/2016/05/09/30-days-of-zumo-v2-azure-mobile-apps-day-18-asp-net-authentication/ - it adjusts the table controller to do exactly what you want.
I have a class in my MVC5 application that deals with some user related functionality and has a dependency on HttpContext.Current.User as shown below
public interface IUser
{
// return roles of currently logged in user
string[] GetRoles;
}
public Class User : IUser
{
private HttpContext context;
// constructor
public User(HttpContext user)
{
this.context = user
}
// get roles
public string[] GetRoles()
{
string username = this.context.User.Identity.Name;
// get roles through some DB calls
string[] roles = someDbCalls();
return roles;
}
}
I have it setup for dependency injection using Ninject in NinjectWebCommon.cs as
kernel.Bind<IUser>().To<User>().WithConstructorArgument("user", x => HttpContext.Current);
This works fine if called from anywhere in my code except in my custom RolesProvider which is setup as shown below
public class CustomRoleProvider : RoleProvider
{
[Inject]
public IUser user {get; set;}
public override string[] GetRolesForUser(string username)
{
return this.user.GetRoles();
}
}
The call to GetRoles() from my custom role provider fails because HttpContext.Current.User injected by Ninject under this case is null. Any idea on what I may be doing wrong?
Edit:
On further testing, it appears that the problem is with the way I am using Ninject in my custom Roles provider. Using the attribute injection as shown below
[Inject]
public IUser user {get; set;}
works only the first time and subsequent calls fail with HttpContext.Current.User is null error. I have fixed it in a hacky way by forcing the injection to happen each time I call the GetRoles method as shown below
public class CustomRoleProvider : RoleProvider
{
private IUser user;
public override string[] GetRolesForUser(string username)
{
// force ninject to inject a new instance of my interface
var user = DependencyResolver.Current.GetService<IUser>();
return user.GetRoles();
}
}
Not sure why this works and so I am leaving this question open if someone can provide an explanation.
It appears that by the time the role provider is called, the HTTPContext.Current is not yet set. This leads to other issues with custom RolesProvider (like the Null Reference Exception due to EtwTracing bug see: SqlRoleProvider on IIS8 Express
).
If you really need the HTTPContext.Current instead of using the Thread's PrincipalIdentity, you can setup your app to use compatibility mode. This appears to resolve the problem by setting up the HttpContext.Current sooner:
https://social.msdn.microsoft.com/Forums/en-US/8ee88c92-5e8a-4c66-ace7-887eb500e1cb/httpcontextcurrent-always-been-null
I am authenticating users via GoogleOpenIdOAuthProvider. I need to access the email address of the user that logged in. I have attempted to implement the Using Typed Sessions in ServiceStack code as-is.
So, I created a base class that my service inherits from:
public abstract class AppServiceBase : Service
{
//private CustomUserSession userSession;
protected CustomUserSession UserSession
{
get
{
return base.SessionAs<CustomUserSession>();
}
}
}
public class CustomUserSession : AuthUserSession
{
public string CustomId { get; set; }
}
The service has the [Authenticate] attribute on it. In my AppHost setup, I have configured auth like this:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new GoogleOpenIdOAuthProvider(appSettings) //Sign-in with Google OpenId
}));
Once the user has authenticated, the service tries to access the auth session from the base class like this:
var x = base.UserSession.Email;
However, Email is always null. How can I access this value?
You will need to pull the data from the AuthProvider and set the value in the CustomUserSession. An example of this is shown in the SocialBootstrapApi sample
https://github.com/ServiceStack/SocialBootstrapApi/blob/master/src/SocialBootstrapApi/Models/CustomUserSession.cs#L50
Override OnAuthenticated, find the GoogleOpenIdOAuthProvider to get to the email address.
Another example is shown at ServiceStack OAuth - registration instead login
I have created a plugin which inspects a param in the query string and loads up a user object based on this ID and populates
any request DTO with it. (All my request DTO's inherit from BaseRequest which has a CurrentUser property)
public class CurrentUserPlugin : IPlugin
{
public IAppHost CurrentAppHost { get; set; }
public void Register(IAppHost appHost)
{
CurrentAppHost = appHost;
appHost.RequestFilters.Add(ProcessRequest);
}
public void ProcessRequest(IHttpRequest request, IHttpResponse response, object obj)
{
var requestDto = obj as BaseRequest;
if (requestDto == null) return;
if (request.QueryString["userid"] == null)
{
throw new ArgumentNullException("No userid provided");
}
var dataContext = CurrentAppHost.TryResolve<IDataContext>();
requestDto.CurrentUser = dataContext.FindOne<User>(ObjectId.Parse(requestDto.uid));
if (requestDto.CurrentUser == null)
{
throw new ArgumentNullException(string.Format("User [userid:{0}] not found", requestDto.uid));
}
}
}
I need to have this User object available in my services but I don't want to inspect the DTO every time and extract from there. Is there a way to make data from plugins globally available to my services? I am also wondering if there is another way of instantiating this object as for my unit tests, the Plugin is not run - as I call my service directly.
So, my question is, instead of using Plugins can I inject a user instance to my services at run time? I am already using IoC to inject different Data base handlers depending on running in test mode or not but I can't see how to achieve this for User object which would need to be instantiated at the beginning of each request.
Below is an example of how I inject my DataContext in appHost.
container.Register(x => new MongoContext(x.Resolve<MongoDatabase>()));
container.RegisterAutoWiredAs<MongoContext, IDataContext>();
Here is an example of my BaseService. Ideally I would like to have a CurrentUser property on my service also.
public class BaseService : Service
{
public BaseService(IDataContext dataContext, User user)
{
DataContext = dataContext;
CurrentUser = user; // How can this be injected at runtime?
}
public IDataContext DataContext { get; private set; }
public User CurrentUser { get; set; }
}
Have you thought about trying to use the IHttpRequest Items Dictionary to store objects. You can access these Items from any filter or service or anywhere you can access IHttpRequest. See the src for IHttpRequest.
Just be mindful of the order that your attributes, services and plugins execute and when you store the item in the Items dictionary.
Adding:
We don't want to use HttpContext inside of the Service because we want use Service in our tests directly.
Advantages for living without it
If you don't need to access the HTTP
Request context there is nothing stopping you from having your same
IService implementation processing requests from a message queue which
we've done for internal projects (which incidentally is the motivation
behind the asynconeway endpoint, to signal requests that are safe for
deferred execution).
http://www.servicestack.net/docs/framework/accessing-ihttprequest
And we don't use http calls to run tests.
So our solution is:
public class UserService
{
private readonly IDataContext _dataContext;
public UserService(IDataContext dataContext)
{
_dataContext = dataContext;
}
public User GetUser()
{
var uid = HttpContext.Current.Request.QueryString["userId"];
return _dataContext.Get<User>(uid);
}
}
and
container.Register(x => new UserService(x.Resolve<IDataContext>()).GetUser()).ReusedWithin(ReuseScope.Request);
This is service signature:
public SomeService(IDataContext dataContext, User user) { }
Any suggestions?
I need to have this User object available in my services but I don't want to inspect the DTO every time and extract from there
How will your application know about the user if you're not passing the 'userid' in the querystring? Could you store the user data in the Session? Using a Session assumes the client is connected to your app and persists a Session Id (ss-id or ss-pid cookie in ServiceStack) in the client that can be looked up on the Server to get the 'session data'. If you can use the Session you can retrieve the data from your service doing something like
base.Session["UserData"] or base.SessionAs<User>();
Note: you will need to save your User data to the Session
Is there a way to make data from plugins globally available to my services? but I can't see how to achieve this for User object which would need to be instantiated at the beginning of each request.
This sounds like you want a global request filter. You're kind of already doing this but you're wrapping it into a Plugin.