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.
Related
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(), ...));
I was following the instructions on http://leastprivilege.com/2013/11/11/client-certificate-authentication-middleware-for-katana/ but also followed Diminic's Pluralishight video on Web API security as I was trying to apply a client certificate authentication on my self hosted Web API v2 project.
I call the service from Advanced REST Client Chrome extension app, meaning it does not contain a client certificate in the request, and I see that cert == null but after that I still get a valid response from the server.
Is there something missing from this tutorial code?
public class ClientCertificateAuthenticationOptions : AuthenticationOptions
{
public X509CertificateValidator Validator { get; set; }
public bool CreateExtendedClaimSet { get; set; }
public ClientCertificateAuthenticationOptions() : base(“X.509″)
{
Validator = X509CertificateValidator.ChainTrust;
CreateExtendedClaimSet = false;
}
}
public class ClientCertificateAuthenticationHandler :
AuthenticationHandler<ClientCertificateAuthenticationOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var cert = Context.Get<X509Certificate2>(“ssl.ClientCertificate”);
if (cert == null)
{
return Task.FromResult<AuthenticationTicket>(null);
}
try
{
Options.Validator.Validate(cert);
}
catch
{
return Task.FromResult<AuthenticationTicket>(null);
}
var claims = GetClaimsFromCertificate(
cert, cert.Issuer, Options.CreateExtendedClaimSet);
var identity = new ClaimsIdentity(Options.AuthenticationType);
identity.AddClaims(claims);
var ticket = new AuthenticationTicket(
identity, new AuthenticationProperties());
return Task.FromResult<AuthenticationTicket>(ticket);
}
}
public class ClientCertificateAuthenticationMiddleware :
AuthenticationMiddleware<ClientCertificateAuthenticationOptions>
{
public ClientCertificateAuthenticationMiddleware(
OwinMiddleware next,
ClientCertificateAuthenticationOptions options)
: base(next, options)
{ }
protected override AuthenticationHandler<ClientCertificateAuthenticationOptions> CreateHandler()
{
return new ClientCertificateAuthenticationHandler();
}
}
app.UseClientCertificateAuthentication();
app.UseWebApi(WebApiConfig.Register());
Did you decorate your ApiController with the Authorize attribute?
[Authorize]
public class MyWebApiController : ApiController
{
}
Otherwise, the status code of your request will be 200 and not 401 even if the username/password do not match.
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;
}
}
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.
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.