ServiceStack ServerSentEvents restrict access to channel - c#

In my ServiceStack app I would like to deny access to channels for unauthorized users - so even the join event would not fire for an unauthorized client. I am using custom auth provider that does not interact with the DB and is very minimalistic for now (mainly for testing purposes)
public class RoomsAuthProvider : CredentialsAuthProvider
{
private int userId = 0;
public RoomsAuthProvider(AppSettings appSettings) : base(appSettings)
{
}
public RoomsAuthProvider()
{
}
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
if (password == "ValidPassword")
{
return true;
}
else
{
return false;
}
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
//session.CreatedAt = DateTime.Now;
//session.DisplayName = "CustomDisplayName" + userId;
//session.IsAuthenticated = true;
//session.UserAuthName = session.UserName;
//session.UserAuthId = userId.ToString();
//Interlocked.Increment(ref userId);
//authService.SaveSession(session, SessionExpiry);
//return null;
}
}
Main service piece:
[Authenticate]
public class ServerEventsService : Service
{
...
}
sidenote - I have tried overriding the default DisplayUsername to not be username1...usernameN but no luck. My client code is
var client = new ServerEventsClient("http://localhost:1337/", "home")
{
OnConnect = OnConnect,
OnCommand = HandleIncomingCommand,
OnMessage = HandleIncomingMessage,
OnException = OnException,
OnHeartbeat = OnHeartbeat
}.Start();
client.Connect().Wait();
var authResponse = client.Authenticate(new Authenticate
{
provider = "credentials",
UserName = "test#gmail.com",
Password = "p#55w0rd",
RememberMe = true,
});
client.ServiceClient.Post(new PostChatToChannel
{
Channel = "home", // The channel we're listening on
From = client.SubscriptionId, // Populated after Connect()
Message = "Hello, World!",
});
Even if I skip the authenticate call the other clients will still get onJoin command about not authenticated client when it tries to do an unauthorized post (and get an error). Also when I intentionally do multiple unauthorized users counter grows - assigned username becomes username2, username3 and so on - how can I disable unauthorized users COMPLETELY? Marking my DTOs with Authenticate also didn't change anything. Any ideas are welcome as well as crytics as I'm new to ServiceStack and would like to implement the best practices.

There's already an option to limit access to authenticated users only with:
Plugins.Add(new ServerEventsFeature {
LimitToAuthenticatedUsers = true
});

Related

ASP.NET Web API 2 Custom Authorization and Authorize Attribute

I have created a Google Chrome Extension and I plan to communicate with ASP.NET Wep API 2 service. Web service creates encrypted FormsAuthenticationTicket and sends it in the Login/SignIn response for the first time. Validating user whether is authorized or not has been controlled by checking Request Cookie.
Project creates ticket via GetEncryptedTicket(int UserID) function.
How can control and use [Authorize] attribute above the service functions? I know oAuth 2.0 may be the better option to implement it but I want to use following function to create ticket.
public static string GetEncryptedTicket(int UserID)
{
DateTime now = DateTime.UtcNow.ToLocalTime();
var ticket = new FormsAuthenticationTicket(
1,
"TICKET_NAME",
now,
now.Add(FormsAuthentication.Timeout),
true,
UserID.ToString(),
FormsAuthentication.FormsCookiePath
);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
return encryptedTicket;
}
Use Action filter to validate the token like this
public class BasicAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
try
{
if (actionContext.Headers.GetValues("UserToken").FirstOrDefault() == null)
{
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
ReasonPhrase = "Http request doesnot have the UserToken header"
};
}
else
{
string username = string.Empty, password = string.Empty;
var userToken = actionContext.Request.Headers.GetValues("UserToken").FirstOrDefault();
var token = FormsAuthentication.Decrypt(userToken);
if (token.Expired)
{
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)
{
ReasonPhrase = "Invalid User Token"
};
}
}
}
catch (Exception ex)
{
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError)
{
ReasonPhrase = ex.Message
};
}
}
}
And Use [BasicAuthentication] Attribute to validate token and allow Access

How to add a database retrieved value custom claim to JWT Token using ServiceStack

In AppHost.Configure I have the following code:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JwtAuthProvider
{
HashAlgorithm = "HS256",
RequireSecureConnection = requireSecureConnection,
AuthKeyBase64 = _configuration["AuthSettings:JwtAuthKeyBase64"],//Settings.Value.JwtAuthKeyBase64,
ExpireTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireTokensIn"].ToDouble()), // JWT Token Expiry
ExpireRefreshTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireRefreshTokensIn"].ToDouble()), // Refresh Token Expiry,
CreatePayloadFilter = (payload,session) => {
payload["ZipCode"] = "value_from_database_for_user";
}
},
new CustomCredentialsAuthProvider(), //HTML Form post of User/Pass
}));
The above code is standard ServiceStack JwtAuthProvider code.
You can see in the above code that the implementation for the anonymous function bound to CreatePayloadFilter would like to retrieve a value from the database, the user's ZipCode is the value and add that as a custom claim to the token.
For many obvious reasons, implementing the retrieval of the user's ZipCode in the AppHost is not easy, elegant or system/architecturally sound. Also it is not even possible, as I will not have the UserId, AppHost is just startup configuration code ran when the service starts.
You can also see in the above code that I have implemented a CustomCredentialsAuthProvider, I can load session data for the logged in user in CustomCredentialsAuthProvider and ServiceStack will map the session values to hydrate the appropriate JWT claims but I cannot add a custom claim via the ServiceStack session object, here is the implementation of CustomCredentialsAuthProvider:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
return true;
//throw new NotImplementedException();
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
session.LastName = "some_lastname_from_db";
session.Roles = new List<string> {"role1", "role2"};
session.Permissions = new List<string> { "permission1", "permission2" };
session.Email = "test#test.com";
session.AuthProvider = "credentials";
//...
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
//Alternatively avoid built-in behavior and explicitly save session with
//authService.SaveSession(session, SessionExpiry);
//return null;
}
}
How can I add a custom claim to the ServiceStack framework created JWT, the value of that claim coming from the database and this implementation of this code not be function binding in AppHost.cs where I do not even have the UserId to retrieve the value anyways?
It sounds like you want to use a Custom UserSession to hold the additional metadata, you can tell ServiceStack to use your Custom Session when you register the AuthFeature, e.g:
Plugins.Add(new AuthFeature(() => new CustomUserSession(), ...)
After which you can cast the UserSession to your CustomUserSession to access the additional properties.
To add additional metadata to the JWT Token you'd use CreatePayloadFilter to add the data to the JWT Token and a corresponding PopulateSessionFilter to populate your Custom UserSession with the additional data.
I wanted to provide my final code for this even though #mythz provided the answer:
The CustomerUserSession:
[DataContract]
public class CustomUserSession: AuthUserSession
{
[DataMember]
public string ZipCode { get; set; }
}
The CustomCredentialsAuthProvider because I have a legacy db with all account info:
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
return true;
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
var customUserSession = (CustomUserSession) session;
//Fill IAuthSession with data you want to retrieve in the app eg:
customUserSession.FirstName = "some_firstname_from_db";
customUserSession.LastName = "some_lastname_from_db";
customUserSession.Roles = new List<string> {"role1", "role2"};
customUserSession.Permissions = new List<string> { "permission1", "permission2" };
customUserSession.Email = "test#test.com";
customUserSession.AuthProvider = "credentials";
customUserSession.CreatedAt = DateTime.UtcNow;
customUserSession.ZipCode = "92123";
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
And my AppHost adding of the ServiceStack AuthFeature:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new JwtAuthProvider
{
HashAlgorithm = "HS256",
RequireSecureConnection = requireSecureConnection,
AuthKeyBase64 = _configuration["AuthSettings:JwtAuthKeyBase64"],//Settings.Value.JwtAuthKeyBase64,
ExpireTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireTokensIn"].ToDouble()), // JWT Token Expiry
ExpireRefreshTokensIn = TimeSpan.FromHours(_configuration["AuthSettings:ExpireRefreshTokensIn"].ToDouble()), // Refresh Token Expiry,
CreatePayloadFilter = (payload,session) => {
payload["zipCode"] = ((CustomUserSession)session).ZipCode;
},
PopulateSessionFilter = (session, token, req) => {
((CustomUserSession) session).ZipCode = token["zipCode"];
}
},
new CustomCredentialsAuthProvider() //HTML Form post of User/Pass
}));

Get Session from SignalR.AuthorizeAttribute to SignalR.Hub

I am working on a SignalR.Hub and I have custom Authorization in an SignalR.AuthorizeAttribute. I have been trying to pass the session I have to retrieve to confirm the User is Authenticated to use the Hub.
I've looked through all of the properties and it seems that they are mostly read-only. I can add something to SignalR.IRequest.Environment but it doesn't appear to be thread-safe and seems improper.
Could I extend the HubCallerContext + Everything that uses it in a way I can tack on my session?
The custom auth
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class HubAuthorize : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return VerifySession(request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
//Could I add something to the HubIncomingInvokerContext?
return VerifySession(hubIncomingInvokerContext.Hub.Context.Request);
}
public bool VerifySession(IRequest request)
{
bool success = false;
string token = "";
bool isApiToken = false;
// Check for token Header Auth
if (string.IsNullOrWhiteSpace(token))
{
token = request.QueryString["X-Custom-Token"];
if (string.IsNullOrWhiteSpace(token))
{
token = request.QueryString["X-Custom-Token"];
isApiToken = true;
}
}
SessionResponse session = null;
if (!string.IsNullOrWhiteSpace(token))
{
session = isApiToken ? ValidateApiToken(token) : ValidateToken(token);
}
if (session != null)
{
//Add Session to request! So I dont have to hit the db again..
//request.Add(new KeyValuePair<string, object>("session", session));
success = true;
}
return success;
}
//... other methods that aren't relevant
}
The Hub
[HubAuthorize]
public class NotificationHub : Hub
{
public void Send(string name, string message)
{
// Use the session here
Clients.All.broadcastMessage(name, message);
}
public override Task OnConnected()
{
Console.WriteLine(Context.ConnectionId);
return base.OnConnected();
}
}
Why have custom authorization if you cant use that to retrieve a session when you verify you are authenticated? Maybe I'm missing something but its pretty frustrating. /endrant
Seems you can't. I still verify the session in HubAuthorize.AuthorizeHubConnection, then in the NotificationHub.OnConnected I build a dictionary of Context.ConnectionId as the key and retrieve the session a secondary time for the value. Feels hackish but there doesn't seem to be a good way to prevent people from accessing the hub without using the .Net built in auth.

How can I get information about the user using a token

There is a need to receive user data using a token. Hello. There is a need to receive user data using a token. I have a web api + websockets, websockets connection via a web browser.
var webSocket = new WebSocket(handlerUrl);
//Open connection handler.
webSocket.onopen = function () {
webSocket.send("{\"type\":\"LOGIN\",\"access_token\":\"Bearer HIDDEN\"}");
};
Once connected, I immediately send token.
On the server side, it looks as follows:
public class SocketClientController: ApiController
{
 public HttpResponseMessage Get ()
 {
HttpContext.Current.AcceptWebSocketRequest (new WebSocketHandler ());
return Request.CreateResponse (HttpStatusCode.SwitchingProtocols);
}
<miss>
Socket class:
<miss>
private void Login(string access_token)
{
// here i want get user info
}
public override void OnMessage(string input)
{
dynamic data = JObject.Parse(input);
switch ((string)data.type)
{
case "LOGIN":
Login((string)data.access_token);
break;
}
}
I use Identity, a variant with a token when you first received from the client suits me the data. Tell me how you can get the user input without going through the login and password, and use [Authorize].
Sorry for my english.
I decided my task! Below is the code:
public class MachineKeyProtector : Microsoft.Owin.Security.DataProtection.IDataProtector
{
private readonly string[] _purpose =
{
typeof(OAuthAuthorizationServerMiddleware).Namespace,
"Access_Token",
"v1"
};
public byte[] Protect(byte[] userData)
{
throw new NotImplementedException();
}
public byte[] Unprotect(byte[] protectedData)
{
return System.Web.Security.MachineKey.Unprotect(protectedData, _purpose);
}
}
Use:
var secureDataFormat = new TicketDataFormat(new Providers.MachineKeyProtector());
AuthenticationTicket ticket = secureDataFormat.Unprotect(access_token);
var userId = ticket.Identity.GetUserId();

ServiceStack API and ASP MVC Authentication in two ways

I'm having trouble solving architecture of an ASP MVC application that servers html pages and web services through ServiceStack.
The application lives in the base url eg "http://myapplication.com" and SS lives in "http://myapplication.com/api" because it is the easiest way to configure both.
In general everything works fine, but when I reached the part of the authorization and authentication, is where I'm stuck.
For one, I need the application handle cookies as ASP normally do FormsAuthentication through, and users would go through a login screen and could consume actions and controllers when the attribute "Authorize" is used. This is typical of ASP, so I have no problem with it, such as "http://myapplication.com/PurchaseOrders".
On the other hand, clients of my application will consume my web service api from javascript. Those web services will also be tagged in some cases with the attribute "Authenticate" of ServiceStack. For example "http://myapplication.com/api/purchaseorders/25" would have to validate if the user can view that particular purchase order, otherwise send a 401 Unauthorized so javascript can handle those cases and display the error message.
Last but not least, another group of users will make use of my API by a token, using any external application (probably Java or .NET). So I need to solve two types of authentication, one using username and password, the other by the token and make them persistant so once they are authenticated the first time, the next calls are faster to solve from the API.
This is the code that I have so far, I've put it very simply to make clear the example.
[HttpPost]
public ActionResult Logon(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
JsonServiceClient client = new JsonServiceClient("http://myapplication.com/api/");
var authRequest = new Auth { provider = CredentialsAuthProvider.Name, UserName = model.UserName, Password = model.Password, RememberMe = model.RememberMe };
try
{
var loginResponse = client.Send(authRequest);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(loginResponse.UserName, false, 60);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
Response.Cookies.Add(cookie);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Test");
}
}
catch (Exception)
{
ModelState.AddModelError("", "Invalid username or password");
}
}
return View();
}
As for the authentication provider I am using this class
public class MyCredentialsAuthProvider : CredentialsAuthProvider
{
public MyCredentialsAuthProvider(AppSettings appSettings)
: base(appSettings)
{
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
if (userName == "testuser" && password == "nevermind")
{
return true;
}
else
{
return false;
}
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
//Fill the IAuthSession with data which you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
session.CreatedAt = DateTime.Now;
session.DisplayName = "Mauricio Leyzaola";
session.Email = "mauricio.leyzaola#gmail.com";
session.FirstName = "Mauricio";
session.IsAuthenticated = true;
session.LastName = "Leyzaola";
session.UserName = "mauricio.leyzaola";
session.UserAuthName = session.UserName;
var roles = new List<string>();
roles.AddRange(new[] { "admin", "reader" });
session.Roles = roles;
session.UserAuthId = "uniqueid-from-database";
//base.OnAuthenticated(authService, session, tokens, authInfo);
authService.SaveSession(session, SessionExpiry);
}
}
On the Configure function of AppHost I am setting my custom authentication class to use it as the default. I guess I should create another class and add it here as well, to handle the token scenario.
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new MyCredentialsAuthProvider(appSettings)
}, htmlRedirect: "~/Account/Logon"));
So far, ServiceStack is working as expected. I can submit a post to /auth/credentials passing username and password and it stores this information, so next call to a service the request is already authorized, great so far!
The question I need to know is how to call (and probably set somewhere in SS) the user that is logging in from my Account controller. If you see the first block of code I am trying to call the web service (looks like I am doing it wrong) and it works, but the next call to any web service looks unauthenticated.
Please don't point me to ServiceStack tutorials, I've been there for the last two days and still cannot figure it out.
Thanks a lot in advance.
Here is what I usually use:
You can replace the "Logon" action method with the code below:
public ActionResult Login(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
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)
{
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
...and the plugins:
//Default route: /auth/{provider}
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
new CustomBasicAuthProvider()
}));
....the Auth provider classes:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return UserLogUtil.LogUser(authService, userName, password);
}
}
public class CustomBasicAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return UserLogUtil.LogUser(authService, userName, password);
}
}
...finally, the logging utility class
internal static class UserLogUtil
{
public static bool LogUser(IServiceBase authService, string userName, string password)
{
var userService = new UserService(); //This can be a webservice; or, you can just call your repository from here
var loggingResponse = (UserLogResponse)userService.Post(new LoggingUser { UserName = userName, Password = password });
if (loggingResponse.User != null && loggingResponse.ResponseStatus == null)
{
var session = (CustomUserSession)authService.GetSession(false);
session.DisplayName = loggingResponse.User.FName.ValOrEmpty() + " " + loggingResponse.User.LName.ValOrEmpty();
session.UserAuthId = userName;
session.IsAuthenticated = true;
session.Id = loggingResponse.User.UserID.ToString();
// add roles and permissions
//session.Roles = new List<string>();
//session.Permissions = new List<string>();
//session.Roles.Add("Admin);
//session.Permissions.Add("Admin");
return true;
}
else
return false;
}
}

Categories