Extending service stack authentication - populating user session with custom user auth meta data - servicestack

I am trying to extend Service Stack's authentication and registration features. I have the authentication and registration working fine, however I need to add some custom data for each user. From Service Stack's documentation and various other posts I found you can add your own data using the MetaData column built into the UserAuth table.
I created a CustomAuthRepository so I can set the meta data property of UserAuth, here is my custom repo:
public class CustomAuthRepository : OrmLiteAuthRepository, IUserAuthRepository
{
public UserAuth CreateUserAuth(UserAuth newUser, string password)
{
newUser.Set(new LoginInfo
{
IsActive = false,
PasswordNeedsReset = true
});
return base.CreateUserAuth(newUser, password);
}
}
This is working great for setting the meta data, I end up with a serialized version of the LoginInfo object in the meta data column of the UserAuth table.
Now what I am trying to do is when a user authenticates I need to change the AuthResponse based on some of that meta data. For example, if a user is not yet activated I want to return an AuthResponse with a property IsActive = get value from custom meta data
I figure I could do this if I can get my custom metadata into the AuthSession. That way in my custom credentials auth provider I could change the response object based on what's in the AuthSession:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var customUserAuthSession = (CustomUserAuthSession)session;
if (!customUserAuthSession.LoginInfo.IsActive)
{
return new
{
UserName = customUserAuthSession.UserName,
IsActive = customUserAuthSession.LoginInfo.IsActive
};
}
var isAuthenticated = base.Authenticate(authService, session, request);
return isAuthenticated;
}
}
Am I going about this the right way, or is there a better way to store and retrieve custom meta data?
How can I change the AuthResponse based on a user's custom meta data?
How can I get my custom meta data into the AuthSession?
Edit
I am getting closer to what I am trying to do. In my CustomAuthSession OnAuthenticated() method :
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
var customUserAuthSession = (CustomUserAuthSession) session;
var userAuth = authService.ResolveService<IUserAuthRepository>().GetUserAuth(session, null);
customUserAuthSession.LoginInfo = userAuth.Get<LoginInfo>();
authService.SaveSession(customUserAuthSession);
base.OnAuthenticated(authService, session, tokens, authInfo);
}
I am refetching the UserAuth and populating the session with the data that I need. Based on the service stack documentation for a custom user session, you need to save the session after you populate it with some custom data. I am doing that but it doesn't seem to be saving.
In my CustomCredentialsAuthProvider, Authenticate method, I don't see the custom data I've added to the session.
Edit
The problem with my first edit above is that the user gets authenticated, then we get to the CustomAuthSession code where I can check if they are active or not. In the case they are not active I would need to log them out, not ideal.
I found instead that I can do all of this in the Authenticate method of my custom CredentialsAuthProvider.
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
var loginInfo = userAuth.Get<LoginInfo>();
if (!loginInfo.IsActive)
{
return new CustomAuthResponse
{
UserName = userAuth.UserName,
ResponseStatus = new ResponseStatus("500"),
IsActive = loginInfo.IsActive
};
}
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
return authResponse;
}
When the request comes in I can use the username in the request to fetch the UserAuth, and check if the user IsActive or not. If not then I can return some error before Service Stack authenticates them.
I think this works well enough for what I am trying to do. I should be able to return an error to the client saying the user is not active.
If anyone has a cleaner way to do this that would be great.

Here is my answer so far. It works and I can do what I am trying to do, but I would love to hear from some of the Service Stack guys as to whether this is the best way to go about this.
To Save custom meta data
Create a new class that subclasses the OrmLiteAuthRepository. In my case I just want to use Service Stack's built in Sql database persistence and have it create the tables needed.
Re-implement the CreateUserAuth method to save any custom metadata :
public UserAuth CreateUserAuth(UserAuth newUser, string password)
{
newUser.Set(new AccountStatus
{
IsActive = false,
PasswordNeedsReset = true
});
return base.CreateUserAuth(newUser, password);
}
Fetching custom meta data
Create a new class that subclasses the CredentialsAuthProvider. Override the Authenticate method.
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
var accountStatus= userAuth.Get<AccountStatus>();
if (!accountStatus.IsActive)
{
throw new InvalidOperationException(string.Format("User {0} is not activated.", request.UserName));
}
if (!accountStatus.PasswordNeedsReset)
{
throw new InvalidOperationException("Your password needs to be reset before you can login.");
}
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
return authResponse;
}
When an authentication request comes into this method, fetch the UserAuth and the custom meta data. If the user is inactive, or their password needs to be reset throw an InvalidOperationException with an error message.
In my client application's Login controller I can check the error message coming back from the service and redirect the user to some page saying there account isn't active yet, or their password needs to be reset before they can be authenticated.

Related

ServiceStack MQ: how to populate data in RequestContext

I'm developing a JWT-based multi-tenancy system using ServiceStack. The JWT token contains shard information, and I use JwtAuthProvider to translate the JWT token to session object following instructions at http://docs.servicestack.net/jwt-authprovider.
Now, I want to use ServiceStack MQ for asynchronous processing. The MQ request needs to be aware of the shard information, so I populate the request context before executing it as follow
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
var sessionKey = SessionFeature.GetSessionKey(m.GetBody().SessionId);
var session = HostContext.TryResolve<ICacheClient>().Get<Context>(sessionKey);
req.Items[Keywords.Session] = session;
var response = ExecuteMessage(m, req);
return response;
});
Here, Context is my custom session class. This technique is stemmed from the instruction at http://docs.servicestack.net/messaging#authenticated-requests-via-mq. Since I execute the message within the context of req, I reckon that I should then be able to resolve Context as follow
container.AddScoped<Context>(c =>
{
var webRequest = HostContext.TryGetCurrentRequest();
if (webRequest != null)
{
return webRequest.SessionAs<Context>();
} else
{
return HostContext.RequestContext.Items[Keywords.Session] as Context;
}
});
However, HostContext.RequestContext.Items is always empty. So the question is, how to populate HostContext.RequestContext.Items from within message handler registration code?
I've tried to dig a little bit into ServiceStack code and found that the ExecuteMessage(IMessage dto, IRequest req) in ServiceController doesn't seem to populate data in RequestContext. For my case, it is a bit too late to get session inside service instance, as a service instance depends on some DB connections whose shard info is kept in session.
The same Request Context instance can't be resolved from the IOC. The Request Context instance is created in the MQ's RegisterHandler<T>() where you can add custom data in the IRequest.Items property, e.g:
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
req.Items[MyKey] = MyValue; //Inject custom per-request data
//...
var response = ExecuteMessage(m, req);
return response;
});
This IRequest instance is available throughout the Request pipeline and from base.Request in your Services. It's not available from your IOC registrations so you will need to pass it in as an argument when calling your dependency, e.g:
public class MyServices : Service
{
public IDependency MyDep { get; set; }
public object Any(MyRequest request) => MyDep.Method(base.Request, request.Id);
}

External Login WebAPI2 MVC5

I need to use a 3rd party token as a way to authenticate. I'm getting the token in the home controller and need to be able to send it on to my WebAPI controllers (It's an SPA application using Backbone). Is there a way of doing this?
EDIT:
Relevant code:
public ActionResult Index(string projectId, int companyId, bool isCompanyAdmin)
{
// if not a valid user return error
var validate = new Validate().ValidateContext(HttpContext,
"Key", "Secret");
if (!validate.IsValidated) return View(Constants.ValidationFailed);
// The info validated, so now I can set it to authorized
// put code here for doing it
//Get the model for the user
try
{
var model = ConvertToVM(_smsRepository.GetCompany(companyId, projectId));
}
catch (ProviderIncompatibleException)
{
// connection string wrong
return View(Constants.ConnectionFailed);
}
catch (Exception e)
{
// catch all
return View(Constants.DatabaseError);
}
//create and send through the view model that determines what view the user will get
return View(model);
}
Ok I put in the index method on the Home Controller. Like I said, we make a call to a third party API passing in the context, the client key, and the client secret to verify our identity. Could I just add a Bearer token in the home controller? Or otherwise pass the http context to OWiN and use some custom logic to add the token if validate.IsValidated is true? It needs to be something that works with WebAPI.

ServiceStack Ws-Security Auth Provider

I'm trying to figure out how to support ws-security as authentication mechanism in SS.
My goal is to have all DTO handled in json,xml,saop11,saop12(that part has been achieved following the SS documentation) and supporting multiple auth providers including one based on ws-security.
DTOs should not be affected at all by the authentication mechanism.
In case the DTO will be sent using saop12, the soap message will be the the call sample generated by the metadata endpoint(soap envelope + soap body) plus a soap header including the ws-security element for the WS-Security Username Authentication. A dedidcated "soap auth provider" should inspect the message, use the soap header -> security element and perform the authentication.
Along with the soap auth provider, I may have other built-in auth mechanism that may used for json message and/or other formats.
Exists a SS auth provider based on ws-security that I'm not aware of?
Any guidelines, suggestions, thoughts to implement it?
At the present than my solution
//AppHost
Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
new SoapMessageAuthProvider(),
}
));
// required by the SoapMessageAuthProvider to inspect the message body serching for ws-security element
PreRequestFilters.Add((httpReq, httpRes) =>
{
httpReq.UseBufferedStream = false;
});
I based the SoapMessageAuthProvider on the built-in BasicAuthProvider.
Since the SoapMessageAuthProvider requires to inspect the incoming message on each call serching for ws-security element, I implemented IAuthWithRequest
public void PreAuthenticate(IRequest req, IResponse res)
{
//Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
SessionFeature.AddSessionIdToRequestFilter(req, res, null);
var userPass = ExtractSoapMessageUserNameCredentials(req);//req.GetBasicAuthUserAndPassword();
if (userPass != null)
{
var authService = req.TryResolve<AuthenticateService>();
//var response = authService.Post(new Authenticate
//{
// provider = Name,
// UserName = userPass.Value.Key,
// Password = userPass.Value.Value
//});
authService.Request = req;
var session = authService.GetSession(false);
var userName = userPass.Value.Key;
//Add here your custom auth logic (database calls etc)
var userAuth = new UserAuth();
userAuth.Id = 10;
userAuth.UserName = userName;
var holdSessionId = session.Id;
session.PopulateWith(userAuth); //overwrites session.Id
session.Id = holdSessionId;
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.UserAuthName = userName;
}
}
//called by CustomAuthUserSession.IsAuthorized
// to be reviewed to keep isolated from other providers
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName))
{
return false;
}
}
return !session.UserAuthId.IsNullOrEmpty();//filled by PreAuthenticate
}
the custom session calls each provider, including the SoapMessageAuthProvider that meanwhile, through the PreAuthenticate method, filled out the session with authenticated user data.
public class CustomAuthUserSession : AuthUserSession
{
public override bool IsAuthorized(string provider)
{
var tokens = ProviderOAuthAccess.FirstOrDefault(x => x.Provider == provider);
return AuthenticateService.GetAuthProvider(provider).IsAuthorizedSafe(this, tokens);
}
...
}
I need to make sure the soap provider will be always invoked for soap message w/ ws-security and the call should not be authenticated by other providers:
- user get authentication through the CustomCredentialsAuthProvider(cookie based)
- user call the service supply json message within the web request that carries the auth cookie
- a further call sends a soap message carrying the same auth cookie: since the message is in soap format and includs the soap header ws-security, the call should be authenticated only using the soap provider using the soap header ws-security within the message.
I understand that a weird scenario, but I'm trying to understand how to accomplish it.
My guess that happends through the ServiceStack.AuthenticateAttribute line 72
matchingOAuthConfigs.OfType<IAuthWithRequest>()
.Each(x => x.PreAuthenticate(req, res));

ASP.Net Identity 2 Reset password with SMS

I'm looking to send the user an SMS when reseting their password. I already have the facilities to send a SMS, I just need a guide on how to set it up with Identity 2.0. I can't seem to find any useful info online, the reference code itself isn't properly commented either.
I want to generate a security code, send it to the user, he must then input it into a form and then be allowed to reset his/her password. Can anyone direct me to a guide/tutorial that explains this process?
After digging in the identity source code i found an alternative token provider that can generate tokens similar to phone number confirmation (six digits).
I had to implement two methods in my UserManager to generate the code and then to validate it.
I declared the token provider inside the UserManager
private TotpSecurityStampBasedTokenProvider<User, string> smsResetTokenProvider = new TotpSecurityStampBasedTokenProvider<User, string>();
This is the first method to generate the code:
public async Task<string> GenerateSMSPasswordResetToken(string userId)
{
var user = await base.FindByIdAsync(userId);
var token = await smsResetTokenProvider.GenerateAsync("Reset Password", this, user);
return token;
}
This is the second method to validate the code:
public async Task<IdentityResult> SMSPasswordResetAsync(string userId, string token, string newPassword)
{
var user = await base.FindByIdAsync(userId);
var valid = await smsResetTokenProvider.ValidateAsync("Reset Password", token, this, user);
if (valid)
{
var passwordStore = Store as IUserPasswordStore<User, string>;
var result = await UpdatePassword(passwordStore, user, newPassword);
if (!result.Succeeded)
{
return result;
}
return await UpdateAsync(user);
}
else
{
return IdentityResult.Failed("InvalidToken");
}
}
You may need to tweak the code depending on your user manager

ServiceStack CredentialAuthProvider with more than User/Password

I want to use a custom auth provider, but I don't see how I can make the standard Auth stuff handle more that user and password as parameters.
Can this be done?
Depends what you want to do, if you want to create your own Custom auth provider by inheriting from CredentialsAuthProvider, you can access different request params via the IHttpRequest object:
public virtual bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
var httpReq = authService.RequestContext.Get<IHttpRequest>();
var fromQueryString = httpRequest.QueryString["CustomField"];
var fromPostData = httpRequest.FormData["CustomField"];
var fromEither = httpRequest.GetParam("CustomField"); //Ext method
}
Here are some other related questions and answers that show different ways to customize ServiceStack's built-in Auth:
How can I extend ServiceStack Authentication
Populating IAuthSession with data from the database

Resources