Possible to bypass servicestack's authentication mechanisms for non standard authentication - c#

My authentication mechanism is in a way different it cannot fit into 1 of ServiceStack's current authentication methods (even overriding method 'TryAuthenticate' does not provide a solution). So would it be possible to authenticate from some arbitrary ServiceStack service?
To give an example:
I open a plain old HTML login page (I am using Angular for the record).
I login in and call my custom ServiceStack service in order to send the non-standard credentials to the server (of course using Angular's http directive).
I validate the credentials myself. If correct I like to hook up into servicestack authentication mechanism and probably have to send back a ServiceStack authentication cookie to the browser. Am I correct?
If someone can make 3 work I can call ServiceStack services which have the authenticate attribute

To be allowed through the [Authenticate] attribute, it needs any one of the registered AuthProviders IsAuthorized() to return true, i.e:
public class CustomAuthProvider : AuthProvider
{
public CustomAuthProvider()
{
this.Provider = "custom";
}
public override bool IsAuthorized(
IAuthSession session, IAuthTokens tokens, Authenticate request=null)
{
return true; //custom logic to verify if this session is authenticated
}
public override object Authenticate(
IServiceBase authService, IAuthSession session, Authenticate request)
{
throw new NotImplementedException();
}
}
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomAuthProvider()
}));
In your Custom Authentication Service you should also save the Users Session with IsAuthenticated=true, e.g:
public object Any(CustomAuth request)
{
//Authenticate User
var session = base.SessionAs<CustomUserSession>();
session.IsAuthenticated = true;
this.SaveSession(session);
}

Related

IdentityServer4 and UserInfo endpoint customization

I have created an IdentityServer4 application, if I login inside that application the user claims are all good. If I login from another client application (MVC) the UserInfo endpoint doesn't return the same claims.
The IdentityServer is configured with ASP.NET Identity, so the UserProfile is already configured to return all UserClaims, like the one I created.
I don't understand why it's not showed on consent view or it's not included in UserInfo endpoint result
Please check for the below points if they can solve your issue
1.) Your Identity resource and API resource should have the required UserClaims.
2.) Check if there is some custom logic to issue requested claims for userinfo endpoint in your profile service.
public class ProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
if (context.Caller == IdentityServerConstants.ProfileDataCallers.UserInfoEndpoint)
{
//custom logic to add requested claims
context.AddRequestedClaims(claims);
}
}
}
3.) Try to make the property 'GetClaimsFromUserInfoEndpoint=true' in your MVC client AddOpenIdConnect configuration.
have you configured your IdentityResources?
Something like:
services.AddIdentityServer()
.AddInMemoryIdentityResources(GetIdentityResources())
//where
public static List<IdentityResource> GetIdentityResources()
{
// Claims automatically included in OpenId scope
var openIdScope = new IdentityResources.OpenId();
openIdScope.UserClaims.Add(JwtClaimTypes.Locale);
// Available scopes
return new List<IdentityResource>
{
openIdScope,
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource(Constants.RolesScopeType, Constants.RolesScopeType,
new List<string> {JwtClaimTypes.Role, Constants.TenantIdClaimType})
{
//when false (default), the user can deselect the scope on consent screen
Required = true
}
};
}

Windows Authentication - require additional password for special users

I am developing an intranet asp.net core web api application. The requirements for authentications are:
REQ1 - when user which is trying to access the website is not in Active Directory's special group (let's name it "commonUsers") it is simply not authorized
REQ2 - when user which is trying to access the website is in Active Directory's group "commonUsers" is is authorized and a web resource is returned
REQ3 - when user which is trying to access the website is in Active Directory's group "superUser", it need to be prompted for his domain password once again (because it tries to access some very restricted resources)
Now, what I have so far:
My service is hosted using http.sys server in order to support windows authentication.
I am using claims transformer middlewere in order to check the user's Active Directory group, let's say something like this:
public class ClaimsTransformer : IClaimsTransformation {
private readonly IAuthorizationService _authorizationService;
public ClaimsTransformer(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
_authorizationService.Authorize(principal as IHmiClaimsPrincipal);
return Task.FromResult(principal);
}}
I have specified a special policies also in my service configuration, for instance something like that:
services.AddAuthorization(options =>
{
options.AddPolicy("TestPolicy", policy =>
policy.RequireClaim(ClaimTypes.Role, "TestUser"));
options.AddPolicy("TestPolicy2", policy =>
policy.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
I am using [Authorize] attribute with specific policy in order to restrict access to specific resources based on policies
Now the question is, how should I satisfy REQ3?
I think I would try to use MVC Filters : https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#authorization-filters
Filters run after all Middleware, but before the Action. This will allow you to control the redirect to credentials page just for specific actions or controllers. Whilst normally this is not the recommended method for authorization, I think it fits your requirements for a hybrid secondary authentication.
public class SuperUserFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Cookies.TryGetValue("SuperUserCookie", out string cookieVal))
{
if (!IsValidCookie(cookieVal))
context.Result = LoginPage(context);
}
else
{
context.Result = LoginPage(context);
}
}
private bool IsValidCookie(string cookieVal)
{
//validate cookie value somehow
// crytpographic hash, store value in session, whatever
return true;
}
private ActionResult LoginPage(AuthorizationFilterContext context)
{
return new RedirectToActionResult("SuperUser", "Login",
new {redirectUrl = context.HttpContext.Request.GetEncodedUrl()});
}
}
Then you create a Login Controller
public class LoginController : Controller
{
[HttpGet]
public IActionResult SuperUser(string redirectUrl)
{
// return a page to enter credentials
// Include redirectUrl as field
}
[HttpPost]
public IActionResult SuperUser(LoginData loginData)
{
// Validate User & Password
Response.Cookies.Append("SuperUserCookie", "SomeValue");
return Redirect(loginData.RedirectUrl);
}
}
Then you can decorate specific actions (or controllers) as required:
public class MyController : Controller
{
[HttpGet]
[SuperUserFilter]
public IActionResult MySensitiveAction()
{
// Do something sensitive
}
}
I'm guessing you are try to implement two step authentication for some of your resource.
To do that you must use multiple authentication scheme and Authorize policies,
but it's difficult because windows authentication is not controllable. we need to use some trick to know this is your second login.
authentication
The Default Authenticaiton Scheme : Windows, it's the basic scheme for authenticate a windows user.
Second Cookies base Authentication scheme : SuperUserTwoStep. we need this to goto our custom login logic.
Authorize
the Authorize policies for specified scheme.
a login page for login to SuperUserTwoStep scheme.
//startup
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme)
.AddCookie("SuperUserTwoStep",op=>op.LoginPath = "/account/superuser2steplogin");
services.AddAuthorization(op =>
{
op.AddPolicy("SuperUser", b => b.AddAuthenticationSchemes("SuperUserTwoStep")
.RequireAuthenticatedUser()
.RequireClaim(ClaimTypes.Role, "SuperUser"));
});
// login
public static IDictionary<string, string> States { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
[Route("/account/superuser2steplogin")]
public async Task<IActionResult> LoginTwoStepConfirm(string returnUrl, [FromServices]IAuthorizationService authorizationService,
[FromServices]IAuthorizationPolicyProvider policyProvider)
{
var winresult = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
if (winresult.Succeeded)
{
if (States.TryGetValue(winresult.Principal.Identity.Name, out _))
{
States.Remove(winresult.Principal.Identity.Name);
var principal = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity(winresult.Principal.Claims,"twostepcookie"));
await HttpContext.SignInAsync("SuperUserTwoStep", principal);
return Redirect(returnUrl);
}
else
{
States[winresult.Principal.Identity.Name] = "1";
return Challenge(IISDefaults.AuthenticationScheme);
}
}
else
{
return Challenge(IISDefaults.AuthenticationScheme);
}
}
[Authorize("SuperUser")]
public IActionResult YourSecurePage()
{
return Content("hello world");
}
the most difficult thing is to track that this is the second time to login, I try to use cookie , but it doen't work, so I crate a static IDitionary<string,string> to track ,maybe use distributed cache is better
I think in my opinion you should consider using: Policy-based authorization with Requirements, basically you have different authorization requirements that you want to treat them on and AND basis
REQ1 and REQ2 and REQ3
Here you have the link to the documentation: Requirements
But you need to understand that identity != permissions, the guys that introduce this concept of policies to Microsoft created a project named: PolicyServer and it is opensource: PolicyServer Git and they created a pattern there of how you should use your policies. Basically, you have external and internal users that are authenticated against your AD, all internal users should have permissions assigned to a role. And you only decorate your controller action with the permission rule you created for that policy
[Authorize("PerformSurgery")]
public async Task<IActionResult> PerformSurgery()
{
// omitted
}
To understand the code and how they evaluate a policy, I think you should see the video they have online on the website: Policy Server
Hope this helps

Very basic bearer token authentication and authorization in Web API 2

I have an intranet site, hosted locally within my organisation. The same site also exposes some data through various web services. It's written using ASP.NET MVC 5 and WebAPI 2, and it's .NET 4.5, not Core.
At the moment users can login to the website using Windows Authentication, and once authenticated they can access the APIs. However, I need to also allow access to the APIs using tokens so that they can be interrogated by automated processes, so I've created a page where authenticated users can go and request a token.
It's my intention that this token can be used as a Bearer token, included in the header of HTTP requests to the Web API, to allow access to the APIs. As I understand it, a Bearer token intrinsically represents the User's right to access the data and doesn't require any other information (even a username).
However, I've struggled to find a complete, end-to-end tutorial for authenticating and authorizing the requests. There are questions on this site and Microsoft aritcles which give some great pointers but I feel that they're perhaps hinting at something much too complicated for my requirements. I don't need to return any kind of Identity with Claims or anything like that, and I'm not concerned with OAuth at all.
I'm using Microsoft's Web API framework so it seems reasonable to assume that it should be fairly straightforward to do something as basic as extract and check a token from the request header!
Would somebody be able to outline the components and the process I need to put in place within my application to allow it to extract the Bearer token from the HTTP request, use my own code to check its validity and then support the Authorize attribute on Web API Methods if the token is valid?
Looks like we have the same need, I also just needed a quick bearer token verification to not leave the API completely wide open.
I copied most parts from here and tweaked it so it just checks the Bearer token https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/authentication-filters
Add filter in WebApiConfig.cs
public class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Add authentication
config.Filters.Add(new SimpleAuthenticationFilter()):
foo
}
}
SimpleAuthenticationFilter.cs
public class SimpleAuthenticationFilter : IAuthenticationFilter
{
private readonly string _bearerToken = ConfigurationManager.AppSettings["simpleToken"];
public bool AllowMultiple { get; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// 1. Look for credentials in the request.
var request = context.Request;
var authorization = request.Headers.Authorization;
// 2. If there are no credentials, do nothing.
if (authorization == null)
{
context.ErrorResult = new AuthenticationFailureResult("Authorization header is 'null''", request);
return;
}
// 3. If there are credentials but the filter does not recognize the
// authentication scheme, do nothing.
if (!authorization.Scheme.Equals("Bearer"))
{
context.ErrorResult = new AuthenticationFailureResult("Authentication type must be 'Bearer'", request);
return;
}
// 4. If there are credentials that the filter understands, try to validate them.
// 5. If the credentials are bad, set the error result.
if (string.IsNullOrEmpty(authorization.Parameter))
{
context.ErrorResult = new AuthenticationFailureResult("Bearer token is null or empty", request);
return;
}
if (!authorization.Parameter.Equals(_bearerToken))
{
context.ErrorResult = new AuthenticationFailureResult("Bearer token invalid", request);
}
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}
AuthenticationFailureResponse.cs
public class AuthenticationFailureResult : IHttpActionResult
{
public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
{
ReasonPhrase = reasonPhrase;
Request = request;
}
private string ReasonPhrase { get; }
private HttpRequestMessage Request { get; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
private HttpResponseMessage Execute()
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
RequestMessage = Request, ReasonPhrase = ReasonPhrase
};
return response;
}
}
Expanding on Min's answer above:
string token = Request.Headers.Authorization.ToString().Split(' ')[1];

SignalR: Receive Custom Id on Every Call Including OnConnected

I am using SignalR to relay messages from a WebAPI server back-end to a JavaScript web page. These messages are only relayed to certain users so I need to map the SignalR ConnectionId with the custom id of the user of the webpage.
Currently the WebAPI uses FormsAuthentication and the custom id I need is in the cookie.
Initially I inherited the IUserIdProvider to pull the value off of the cookie:
public class CustomIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
Cookie formsAuthCookie = request.Cookies[FormsAuthentication.FormsCookieName];
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(formsAuthCookie.Value);
var obj = JsonConvert.DeserializeObject(ticket.Name) as JObject;
return (string)obj.GetValue("UserId");
}
catch (Exception)
{
return null;
}
}
}
which worked as far as getting the custom id correctly. But that value was never set on the identity as far as I could tell. I also was unable to edit any of the Identity values due to Context.User.Identity.Name all being readonly.
Edit: Trying the CustomIdProvider again, I correctly get the value out of the cookie but on returning from the GetUserId method, OnConnected is never called.
My next approach was based off of Shaun Xu's blogpost
Set Context User Principal For Customized Authentication In SignalR.
Here is my implementation:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private const string CUSTOM_IDENTITY_KEY = "server.User";
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
string customId;
// This gets the custom id from the cookie.
TryGetCustomId(request, out customId);
// The CustomIdentity class just sets the customId to the name.
request.Environment.Add(CUSTOM_IDENTITY_KEY, new ClaimsPrincipal(new CustomIdentity(customId, true)));
return true;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
string connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;
IDictionary<string, object> environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
object obj;
environment.TryGetValue(CUSTOM_IDENTITY_KEY, out obj);
var principal = obj as ClaimsPrincipal;
if (principal?.Identity == null || !principal.Identity.IsAuthenticated)
{
return false;
}
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);
return true;
}
and this actually works to set the Context.User.Identity correctly when both methods are invoked.
However, my problem is that when the user first connects and OnConnected is called, it does not call the AuthorizeHubMethodInvocation and therefore the Context.User.Identity is not available in OnConnected.
I want to be able to access the correct Identity containing my custom id at all stages of the SignalR hub: OnConnected, OnDisconnected, and invoked methods.
Does anyone have a good solution for this?
I have some comments:
1) You should not try to establish your custom identity in authorization stage. It should be the concern of the authentication stage.
2) Implementing custom IUserIdProvider and establishing custom identity id of your Context.User.Identity are separate concerns. The custom IUserIdProvider is just to map any of the properties of your Context.User.Identity as the identifier of user in signalR.
So, to fix your problem. Try establishing your custom identity id at authentication stage. There are many ways to do it depending on how you setup your application. For example:
1) Establishing your custom identity id in Application_PostAuthenticateRequest:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
//Provide custom identity id to your HttpContext.Current.User
//In your case, you may extract that information from your authentication ticket.
}
You could look at this post if you need detailed information: ASP.NET MVC - Set custom IIdentity or IPrincipal
2) Using claims identity, you can return the custom id as a claim, everytime when the browser sends a request to your server, your claims identity is re-established. In the example below, I use owin cookie authentication.
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, user.Id.ToString()));
var id = new ClaimsIdentity(claims, "Cookies");
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
Then in your IUserIdProvider implementation, you can extract the corresponding information in your identity (Name property, claim,...) to use as user identifier in your signalR application.

Register custom credentials auth provider in ServiceStack

I was reading this from their documentation which says:
Then you need to register your custom credentials auth provider:
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}
));
My question is: Where do I put this? Also, what is meant by the comment, "HTML Form post of UserName/Password credentials?"
Currently, I have a ServiceStack service which returns JSON when called. I want to add an Authorize attribute above it so that only authorized users will be able to access it.
I have created a class as they suggest:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
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
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";
//...
//Important: You need to save the session!
authService.SaveSession(session, SessionExpiry);
}
}
How can I "register your custom credentials auth provider?"
You register your plugins within the Configure method of your AppHost. The comment was just used in the example that you pulled the code from to suggest that the CustomCredentialsAuthProvider would work with a HTTP POST from a form.
public class MyApphost : AppHostHttpListenerBase
{
public MyApphost() : base("Service Name", typeof(MyApphost).Assembly) {}
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(
() => new AuthUserSession(),
new IAuthProvider[] { new CustomCredentialsAuthProvider()}
));
}
}

Categories

Resources