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;
}
}
Related
I have some people who login through standard ServiceStack authentication and some people whose passwords need to be compared to Active Directory. They use the same CredentialsAuthProvider and I want to be able to make use of the stuff happening in OrmLiteAuthRepository.TryAuthenticateAsync (recording of invalid login attempts etc.).
My solution thus far:
public class MyOrmLiteAuthRepository : OrmLiteAuthRepository
{
public override async Task<IUserAuth?> TryAuthenticateAsync(string userName, string password,
CancellationToken token = new CancellationToken())
{
if (!IsActiveDirectoryLogin)
{
return await base.TryAuthenticateAsync(userName, password, token);
}
var userAuth = await GetUserAuthByUserNameAsync(userName, token).ConfigAwait();
if (userAuth == null)
{
return null;
}
if (IsValidActiveDirectoryCredentials())
{
await this.RecordSuccessfulLoginAsync(userAuth, false, password, token).ConfigAwait();
return userAuth;
}
await this.RecordInvalidLoginAttemptAsync(userAuth, token).ConfigAwait();
}
}
So I have to repeat all the calls in the base leaving room for problems if ServiceStack changes. If IUserAuth.VerifyPassword wasn't an extension method and virtual I would've overridden it and placed the same conditional logic in there.
Is there a more straight forward way to override the password checking?
The standard way is to create a Custom CredentialsAuthProvider, e.g:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override async Task<bool> TryAuthenticateAsync(IServiceBase authService,
string userName, string password, CancellationToken token=default)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
}
}
That you would register in your AuthFeature plugin instead.
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.
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;
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
So I've created a custom CredentialsAuthProvider using ServiceStack as per the examples located here:
https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization
I have the authentication side of things working but I'm not sure how I populate the session with data from the database in the OnAuthenticated method. In the example they show the following:
//Fill the IAuthSession with data which you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
In the TryAuthenticate method I have the username/password, which I can use to authenticate the user against the database, but once it goes to the OnAuthenticated method, how/what do I use to access/retrieve the user information from the database?
I know this is an older thread but it may still be relevant because unfortunately not much has improved since Sep of 2012 in terms of availability of ServiceStack documentation, clarity of examples or even comments in the code. (#mythz: It would be very helpful if you guys could add meaningful summaries to all your classes and methods.)
I struggled with the same dilemma until I looked at the actual code of CredentialsAuthProvider (which in general is pretty much the only way to understand how things work in ServiceStack). The OnAuthenticated is called right after TryAuthenticate inside the Authenticate method, so I figured it's not necessary to make all your DB calls in OnAuthenticated as #mythz suggests in his examples. Instead I placed the code that populates the IAuthSession object right into my implementation of TryAuthenticate, like so:
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
try
{
// Use my own repo to authenticate the user.
var userRepo = authService.TryResolve<IUserRepository>();
var user = userRepo.Authenticate(userName, password);
// Populate session properties with data from my user POCO.
var session = authService.GetSession();
session.Id = user.CurrentSession.ID.ToString();
session.IsAuthenticated = true;
session.CreatedAt = DateTime.UtcNow;
session.DisplayName = session.FirstName = session.LastName = user.FullName;
session.UserAuthName = session.UserName = user.Username;
session.UserAuthId = user.ID.ToString();
}
catch (Exception ex)
{
// Log the exception, etc....
return false;
}
return true;
}
However, you still have to override OnAuthenticated in order to save the cookie in HTTP response (which I assume is required for subsequent requests from the same browser to be authenticated) because the base implementation only sets the cookie if it finds IUserAuthRepository in the IOC container, which in my case won't happen because I use my own repository. So my implementation now looks like this:
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
try
{
// Save the browser cookie.
var httpRes = authService.RequestContext.Get<IHttpResponse>();
if (httpRes != null)
{
httpRes.Cookies.AddPermanentCookie(HttpHeaders.XUserAuthId, session.UserAuthId);
}
// Save the user session object (ServiceStack stores it in the in-memory cache).
authService.SaveSession(session, SessionExpiry);
}
catch (Exception ex)
{
// Log the exception, etc....
}
}
#mythz: Please let me know if the above makes sense or not.
Another good example of a ServiceStack's CustomUserSession is in the SocialBootstrapApi project. Rather than pulling information out of the data, it extracts the information out of the UserSession and populates its own Custom User Table using the registered DB Factory resolved from the AppHost IOC:
authService.TryResolve<IDbConnectionFactory>().Run(db => db.Save(user));
Rather than using it to extract and save data from the user's session, you can also use any of your registered dependencies to fetch data and populate the session with:
public override void OnAuthenticated(
IServiceBase authService,
IAuthSession session,
IOAuthTokens tokens,
Dictionary<string, string> authInfo)
{
using (var db = authService.TryResolve<IDbConnectionFactory>().OpenDbConnection())
{
var user = db.Id<MyUser>(session.UserId);
session.FirstName = user.FirstName;
}
}