Is this the right way to do stateless authentication per call on ServiceStack? - rest

I have REST service requirements in which some calls require authentication and some don't. Absolutely no state is used, as the calls are all independent from one another. I have put something together which seems to work, but is this the right way to go about not using sessions?
This question is kind of related to my WCF question which is answered here.
Firstly I registered the authentication method:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}
));
I then attribute the respective calls (or service or DTO) with the Authenticate attribute:
[Authenticate]
public HelloResponse Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name + " with POST & Auth"};
}
I inherit from the BasicAuthProvider class which does the authentication:
public class CustomCredentialsAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return userName == "dylan" && password == "abc123";
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
session.IsAuthenticated = true;
//Important: You need to save the session!
authService.SaveSession(session, new TimeSpan(0,0,10));
}
}
As you can see, I do save the session but it times out after 10 seconds. This is the part that I'm sure can potentially be done better. It seems to work nicely though.
Is there a better way of doing what I'm trying to accomplish?
Is there also any way, due to the sessionless nature of these services, to remove the Auth, AssignRoles and UnassignRoles methods?

If you wanted to keep using ServiceStack's Authentication and Session support you could just add a response filter that clears the user session after the service is executed, e.g:
this.ResponseFilters.Add((req, res, dto) => req.RemoveSession());
Basically after each request is executed it clears the session, so no record of them having authenticated exists.
Otherwise you can just skip using ServiceStack's Authentication completely and just provide your own via RequestFitlers of FilterAttributes (which is essentially what SS Auth does under the hood).

Related

Using AcceptedAtActionResult with Azure Function to direct to specific method

I have a API that accepts a submission request (that when accepted to return http code 202 Accepted), for some workflow to approve, and then the caller to retrieve the result later.
The URL should be returned on successful submission and I am trying to use AcceptedAtActionResult with Azure Functions, however I do not understand how to use it as they don't have Controllers per my understanding, but this is one of the parameters.
Regardless and trying to use string.Empty, or the class name EnrollmentFunctions, or path enroll, the library is generating strange paths (in this case pointing to a different function in a different file i.e. location: http://localhost:7071/api/attest/c588484e-8e57-47f6-bf6c-973cfa5b9214?action=GetStatus&controller=enroll).
I am looking to return of the form location: http://localhost:7071/api/enroll/c588484e-8e57-47f6-bf6c-973cfa5b9214 (but avoid hardcoding URLs, both for maintenance and also running in dev mode on local machine).
I believe this would get more complicated if we used a cloud WAF (like cloudflare) on the front-end, in that case I presume would create my own class to build the URL (i.e. https://api-protected-by-waf.mydomain.com/enroll/bf920104-a630-4b58-97cb-cc3ae45c31d3)? I can see this then becoming a configuration issue was looking to avoid on the previous para.
Research points to creating my own IUrlHelper and possibly specifying key:values for retrival via Environment.GetEnvironmentVariable("your_key_here") (for use by my IUrlHelper implementation).
Thanks in advance on how to solve this issue or if i am using the libraries incorrectly and alternate best practice. Code of the Azure functions are below:
public class EnrollmentFunctions
{
[FunctionName("Enroll")]
public async Task<IActionResult> Enroll(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "enroll")] HttpRequest req)
{
var id = Guid.NewGuid();
return new AcceptedAtActionResult("GetStatus", "enroll", new { id = id }, id);
// return new AcceptedAtRouteResult("GetStatus", (new { id }, new { Result = id.ToString() }));
}
[FunctionName("GetStatus")]
public async Task<IActionResult> GetStatus(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "enroll/{id}")] HttpRequest req)
{
return new OkObjectResult("OK");
}
}

EF Core - multi tenant application with 2 DB Context

Background:
.NET 6 application (front-end Angular SPA) Will be deployed as single application with 1 database per tenant. There is a shared database (called GlobalContext) which holds Tenant and TenantUser information.
In my Program.cs I don't have a connection string to the tenant database, as that information information is only available after a user has logged in.
builder.Services.AddDbContext<GlobalContext>(
options => options.UseSqlServer(config.GetValue<string>("ConnectionStringGlobal"))
);
No connection string specified here
builder.Services.AddDbContext<FinanceAppContext>();
In the OnConfiguring method in the FinanceappContext I obtain the connection string using a service;
var tenant = await _tenantService.GetConnectionString();
optionsBuilder.UseSqlServer(_config.GetValue<string>(tenant));
base.OnConfiguring(optionsBuilder);
The TenantService is a transient service which obtains the logged in users tenant from the GlobalContext
public async Task<string> GetConnectionString() {
try
{
var userId = _contextAccessor.HttpContext.User.FindFirst(ClaimTypes.Email).Value;
var user = await _globalContext.TenantUser
.Include(tenantuser => tenantuser.Tenant)
.FirstOrDefaultAsync(tenantuser => tenantuser.EmailAddress == userId);
return user.Tenant.Name;
}
catch (System.Exception)
{
return "";
}
}
But when I try to access any data via the FinanceAppContext I get the following error;
A relational store has been configured without specifying either the DbConnection or connection string to use
It seems like the fact I don't specify a connection string in Program.cs and but do specify one in OnConfiguring seems to be an issue?
So the issue was that my OnConfiguring method was marked as async and I was making an await call to my TenantService to obtain the connection string. I remove the async/await and the retrieval of the user from the GlobalContext now works.

custom Shiro realm - who does the actual authentication

when sub classing shiro's AuthorizingRealm (or only AuthenticationRealm) by overriding
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
}
Is it my job to check that the credentials provided in the AuthenticationToken actually match?
Or am I supposed to return the AuthenticationInfo with the principals resolved from the AuthenticationToken and the correct password for the given credentials and shiro will compare them on its own somewhere within the flow of the Subject.login(AuthenticationToken) call?
The Javadocs for AuthenticatingRealm.doGetAuthenticationInfo() state (emphasis mine):
Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given authentication token.
For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific log-in logic in addition to just retrieving data - it is up to the Realm implementation.
The method AuthenticatingRealm.getAuthenticationInfo() first calls doGetAuthenticationInfo() then subsequently calls assertCredentialsMatch() using the configured credentialsMatcher:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
So depending on how typical your Realm implementation is, you might want to avoid checking the AuthenticationToken's credentials in the doGetAuthenticationInfo() method, because the getAuthenticationInfo() template method already contains a step to ensure the submitted credentials match.
To specifically address your question if it is your responsibility "to check that the credentials provided in the AuthenticationToken actually match", the answer is yes, but not in the doGetAuthenticationInfo() method. Typically you would perform the credentials comparison within an implementation of the CredentialsMatcher interface, as described here.
Inside doGetAuthenticationInfo(...) you need to verify that the user has provided you with authentication proof.
Here is a pseudo-coded example of what you might do:
protected AuthenticationInfo doGetAutheticationInfo(AuthenticationToken token) {
if(token instanceof UsernamePasswordToken) {
String username = token.getUsername();
// Look up the user by the provide username
UserRecord userRecord = lookupUserRecord(username);
// No record found - don't know who this is
if (userRecord == null) {
throw new UnknownAccountException();
}
// Check for other things, like a locked account, expired password, etc.
// Verify the user
SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(userRecord.getPrincipal(), userRecord.getHashedCredentials(), userRecord.getCredentialsSalt(), getName());
boolean successfulAuthentication = getCredentialsMatcher().doCredentialsMatch(token, sai);
if(successfulAuthentication) {
// Check for anything else that might prevent login (expired password, locked account, etc
if (other problems) {
throw new CredentialsException(); // Or something more specific
}
// Success!
return sai;
} else {
// Bad password
throw new IncorrectCredentialsException();
}
}
// Don't know what to do with this token
throw new CredentialsException();
}
You'll have to write lookupUserRecord(username) or something similar to go lookup the user information including his hashed and salted credentials.
doGetAuthenticationInfo is the main method where authentication is done. SO if you override it generally you are overriding authentication process. If you want to use the process that was defined for that reealm and do some extra things better call super class method first then get its info and then use it so you will not have to change anything. Also in case of jdbcrealm sqls in shiro.ini are automatically mapped. and they will not be changed until you override
setAuthenticationQuery, setUserRolesQuery, etc
You can easily call following method to simulate the actual process then customize it.
AuthenticationInfo info = super.doGetAuthenticationInfo(token);
Note, that super is a reference to the parent, but super() is it's constructor.
like:
public class CustomJdbcRealm extends JdbcRealm
{
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
AuthenticationInfo info = super.doGetAuthenticationInfo(token);
// Your own code here
}
}

Issues with CurrentUserPropertyBinder it cannot always remember user

I have implemented a CurrentUserPropertyBinder (see below) for a web application using FubuMVC.
public class CurrentUserPropertyBinder : IPropertyBinder
{
private readonly Database _database;
private readonly ISecurityContext _security;
public CurrentUserPropertyBinder(Database database, ISecurityContext security)
{
_database = database;
_security = security;
}
public bool Matches(PropertyInfo property)
{
return property.PropertyType == typeof(User)
&& property.Name == "CurrentUser";
}
public void Bind(PropertyInfo property, IBindingContext context)
{
var currentUser = //check database passing the username to get further user details using _security.CurrentIdentity.Name
property.SetValue(context.Object, currentUser, null);
}
}
When I login to my site, this works fine. The CurrentUserPropertyBinder has all the information it requires to perform the task (i.e. _security.CurrentIdentity.Name has the correct User details in it)
When I try and import a file using fineUploader (http://fineuploader.com/) which opens the standard fileDialog the _security.CurrentIdentity.Name is empty.
It doesn't seem to remember who the user was, I have no idea why. It works for all my other routes but then I import a file it will not remember the user.
Please help! Thanks in Advance
NOTE: We are using FubuMVC.Authentication to authenticate the users
I'm guessing your action for this is excluded from authentication; perhaps it's an AJAX-only endpoint/action. Without seeing what that action looks like, I think you can get away with a simple fix for this, if you've updated FubuMVC.Authentication in the past 3 months or so.
You need to enable pass-through authentication for this action. Out of the box, FubuMVC.Auth only wires up the IPrincipal for actions that require authentication. If you want access to that information from other actions, you have to enable the pass-through filter. Here are some quick ways to do that.
Adorn your endpoint/controller class, this specific action method, or the input model for this action with the [PassThroughAuthentication] attribute to opt-in to pass-through auth.
[PassThroughAuthentication]
public AjaxContinuation post_upload_file(UploadInputModel input) { ... }
or
[PassThroughAuthentication]
public class UploadInputModel { ... }
Alter the AuthenticationSettings to match the action call for pass-through in your FubuRegistry during bootstrap.
...
AlterSettings<AuthenticationSettings>(x => {
// Persistent cookie lasts 3 days ("remember me").
x.ExpireInMinutes = 4320;
// Many ways to filter here.
x.PassThroughChains.InputTypeIs<UploadInputModel>();
});
Check /_fubu/endpoints to ensure that the chain with your action call has the pass-through or authentication filter applied.

How can I call a RIA service from another RIA service?

In my authentication service, I would like to call methods (query or invoke) on my User service to validate credentials. So, for example:
protected override AuthUser ValidateCredentials(string name, string password,
string customData, out string userData)
{
AuthUser user = null;
userData = null;
using (UserService svc = new UserService())
{
if (the result of a call on UserService shows a valid username/password)
{
//Create the user object
user = new AuthUser()
{
Name = name,
UserId = // that user's UserId
};
}
if (user != null)
{
//Set custom data fields for HTTP session
userData = user.UserId.ToString();
}
}
return user;
}
The results I'm finding when searching for things like "call ria service from another ria service" and similar are unrelated to actually calling one from another. Am I doing something wrong from a paradigm point of view? If not, how the heck do you do this? :)
Aggregating DomainServices when all you want to do is Query is pretty easy. Something like
new MyDomainService().GetUser(userName)
should work just fine. However, when you're trying to Submit or Invoke it become trickier because you'll need to initialize and dispose the DomainService. It's been a while since I did this, but I think you can override Initialize and Dispose in your parent DS to call through to the methods in your child DS. For submitting, you won't be able to call the methods directly. Instead you'll need to create a ChangeSet and call the DS.Submit method.
Also, for your scenario, it might be worth checking out the custom authentication sample here. It's a slightly different approach for what you're trying to do.