Context
I have a working WebApi2 application, which uses the out of the box bearer token validation, just as was in the original Visual Studio project template.
I would like to add a custom data to the generated token, then check against that custom data when the subsequent api calls happen what are presenting this token.
For the sake of example say I would like to store the IP address of the caller when the token was created, then when validating the token check if the call which is uses the token have the very same IP.
I found the custom class
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
in my project and I also see that OAuthOptions is configured to use that custom class in start up.
I suppose where to add my custom token data (the ip):
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
here I can add it to the ticket properties as custom data.
Question
However I can not find out in what method to check against the token has this data, and it matches to the actual call's ip, and if not, then regard the token invalid?
You are absolutely right when you decided to implement OAuthAuthorizationServerProvider . Now you need to add something like this:
private ClaimsIdentity CreateIdentity(User user, string authenticationType)
{
var identity = new ClaimsIdentity(authenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Login));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.UserID.ToString())); // or ip instead of user.UserID if you need
return identity;
}
And then use it in your Grant... method (for instance GrantResourceOwnerCredentials) like this:
ClaimsIdentity identity = CreateIdentity(user, context.Options.AuthenticationType);
context.Validated(identity);
Then when request come to your webapi controller you can check your data in your custom Attribute:
Claim userIdClaim = ((ClaimsIdentity)actionContext.ControllerContext.RequestContext.Principal.Identity)
.Claims
.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
hope it helps.
Related
Let's say I have an ASP.NET MVC Core application, and I want to validate certain custom conditions when allowing authentication. For example, an user that provides a valid pair of credentials, but is disabled by the application's administrator, or a flag that indicates the user is up-to-date with his payments, or any other arbitrary condition. Is there a place in ASP.NET Core Identity where I can hook this custom validation? I have to make this work for local and external authentication.
For you case, you could create custom user validators.
Writing a custom validator for ASP.NET Core Identity
Custom User Validator That Requires E-Mail Addresses to End in #example.com
This follows a common pattern within Identity, where you can either implement the interface (in this case IValidator<T>) and provide all of the validation code, or override the base implementation of it and add additional logic by overriding methods as I did here.
public class CustomUserValidator : UserValidator<ApplicationUser>
{
public override async Task<IdentityResult> ValidateAsync(UserManager<ApplicationUser> manager,
ApplicationUser user)
{
IdentityResult baseResult = await base.ValidateAsync(manager, user);
List<IdentityError> errors = new List<IdentityError>(baseResult.Errors);
if (!user.Email.EndsWith("#example.com"))
{
IdentityError invalidEmailError = Describer.InvalidEmail(user.Email);
invalidEmailError.Description += " Email address must end with #example.com";
errors.Add(invalidEmailError);
}
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success;
}
}
Then to plug this in, head over to the Startup.cs file and find the ConfigureServices method. Somewhere before the line starting with services.AddIdentity, add the following line:
services.AddTransient<IUserValidator<ApplicationUser>, CustomUserValidator>();
This will add the implementation of CustomUserValidator to the internal services collection, allowing it to be injected anywhere that an IUserValidator<ApplicationUser> is required.
My ASPNetCore web API is using JWT (Json Web Tokens) for authentication. The JWT token has an external and internal user ID inside it. Having these ID's in the JWT does not concern me, as JWT's can't be tampered with or they become invalid, and the internal ID is not useful anywhere outside the system. Of course, the password is not in the JWT content.
Within the JWT, the external user ID becomes the user's System.Security.Claims.ClaimType.Name. The internal ID is set as a JwtRegisteredClaimName.UniqueName value.
When calls are made to the web API, it is good that the [Authorize] attribute attribute makes sure that the user has authenticated and has a currently valid JWT. The concern I have is that once the user is logged in, there is an opportunity for hacking by using the Web API, sending external or internal user id's as criteria that do not match the currently authenticated user. Some web methods in the controllers accept the internal user ID as part of the request being posted, for example, a call to save user information has the internal user ID inside, used as the key for saving the data. I need to be sure that the authenticated user matches/is the same as the user whose data is being saved via the Web API.
My Question is how and where to best implement this data-level security in my web api? Policies don't seem like they can be applied against the data being passed. Authorization filters don't seem to have access to the message body nor any data bindings. Action filters (Microsoft.ASPNetCore.MVC.Filters) run later, but seem like they may not really be intended for this. Also, how do you access the body of the message that was posted inside an action filter? Or should I always make sure that the user ID is passed to methods as a consistently named parameter that I can access via ActionExecutingContext.ActionArguments?
I've searched many posts and not found any scenarios that match what I'm trying to do.
You can always use Middleware to intercept the call when the Response object has been populated , see code sample form here and here .
Authorization filters could also read the request body with EnableRewind :
public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var request = context.HttpContext.Request;
context.HttpContext.Request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
var requestBody = stream.ReadToEnd();
}
}
}
Also works in action filters :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ReadableBodyStreamAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var request = actionContext.HttpContext.Request;
var route = request.Path.HasValue ? request.Path.Value : "";
var requestHeader = request.Headers.Aggregate("", (current, header) => current + $"{header.Key}: {header.Value}{Environment.NewLine}");
var requestBody = "";
request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
requestBody = stream.ReadToEnd();
}
if (...)
{
var wrongResult = new { error = "Wrong parameters" };
actionContext.Result = new JsonResult(wrongResult);
}
}
}
How to skip the [Authorization](Azure AD authentication) from Web Api controller depends on the value from header request?
[Authorize]
public class ExampleController : ApiController
{
//code
private string _clientid;
var req = Request.Headers;
_clientid = string.IsNullOrEmpty(req.GetValues("clientid").First())
? null :
req.GetValues("clientid").First();
}
The above mentioned _clientid is a header value, I want to skip authentication for some _clientid values.
In my opinion you essentially have an alternative way of authentication.
It would make the most sense to define your own authentication scheme that checks that header and creates a user principal based on that.
Then you can keep on using [Authorize] as usual.
Writing an example would be quite time-consuming, the best I can offer at the moment is the Security repo, which contains all of the built-in authentication schemes, like JWT Bearer here: https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs Check those for examples on how to implement an authentication handler.
You can create a custom authentication handler similar to the one here .
Then, where you add authorization in Startup.cs, you can add a custom policy like this:
services.AddAuthorization(
o =>
{
// create a policy called ApiKeyPolicy which requires a Role defined in
// ApiKeyAuthenticationOptions.
// The policy is used by the API controllers.
o.AddPolicy(
"ApiKeyPolicy", builder =>
{
builder.AddAuthenticationSchemes(
ApiKeyAuthenticationOptions.AuthenticationSchemeName);
});
});
and add the scheme to the services.AddAuthentication builder:
builder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
"ApiKey", "ApiKey", o =>
{
o.AllowedApiKeys = config["Api:AllowedApiKeys"];
o.ApiKeyHeaderName = config["Api:ApiKeyHeaderName"];
});
In my example, I have an ApiKeyAuthenticationOptions class that is configured with some API keys and the http header name to check. In your case, you would probably want the valid client IDs.
Finally, you need to tell the [Authorize] attribute which policy is needed:
[Authorize(Policy = "ApiKeyPolicy")]
In your case, you want to be able to handle both the client IDs and regular auth, so you could add the regular auth scheme to the policy builder expression (the first snippet above).
I am using Owin middleware to implement token-based security for my application. When issuing the access token to the client I would also like to pass User Id along with the token, so that, the client application will know the User Id and will be able to call GetUserById (one of the methods inside UserController) in order to show the user his starting page. The best solution I could come up with so far is just adding User Id to the response header. Take a look at my OAuthAuthorizationServerProvider class, in GrantResourceOwnerCredentialsmethod I am adding User Id to the header, using context.Response.Headers.Add("User-Id", new string[] { "1" })
Here is the implementation of my OAuthAuthorizationServerProviderclass
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
//The actual credential check will be added later
if (context.UserName=="user" && context.Password=="user")
{
identity.AddClaim(new Claim("Id", "1"));
context.Validated(identity);
//Here I am adding User Id to the response header
context.Response.Headers.Add("User-Id", new string[] { "1" });
}
else
{
context.SetError("invalid_grant","The credentials provided are not valid");
return;
}
}
}
Basically the client then will have to read User-Id from the header. Is this a good solution I came up with or there is a better one? Also what if I want to pass the whole User object with all its properties to the response is it possible and how to do this?
Since you store the ID already in the claims, why don't you just decode your token on the client and read out the user id like that? Passing it through the headers could allow tampering with it (security).
Have a look on how you could achieve to decode your token and read the claims. This is a c# example https://contos.io/peeking-inside-your-jwt-tokens-using-c-bf6a729d06c8 but this could also be done even through javascript.
This, assuming you use the JWT-format as token (was not specified in your initial question).
Bad way to store UserID as a response header back to client side. This is a huge security concern.
The best way would be to store it as a Claims.
It is very easy to achieve this and get back the claims in the client side.
In your controller, call this and remember to reference
using Microsoft.AspNet.Identity;
var userId = User.Identity.GetUserId();
I'm trying to design a solution where a ServiceStack server can just use an authentication cookie from ASP.NET. (In reality, it could be any cookie. It's just getting a session ID that it can lookup details using a back channel). The custom auth providers don't seem to be the right direction since they are based on credentials being sent. Instead, a GlobalRequestFilter made more sense to me. In there, I check the cookie, get the external session information, then set them to the ServiceStack session and set IsAuthenticated. This works fine in the request service as it has access to the session details that it needs. Fine so far.
The issue, is that when I decide to lock down services with the Authenticate attribute, it apparently runs the attribute prior to my filter so it always wants to redirect them to login. What is the recommended place to add my logic so it fires before the Authenticate attribute and validates properly?
ServiceStack's [Autenticate] attribute is for use with ServiceStack's AuthProvider model so you'll still want to use a Custom AuthProvider. You can have a look at the IAuthWithRequest Auth Providers in the last release notes for examples of creating Custom Auth Providers that aren't based on using credentials:
JwtAuthProviderReader.cs
ApiKeyAuthProvider.cs
AspNetWindowsAuthProvider.cs
By implementing IAuthWithRequest interface in your AuthProvider the [Authenticate] Request Filter will call PreAuthenticate() to perform any Auth validation before validating whether the User is Authenticated or not. Here you can populate the Users Session if the User is Authenticated, e.g:
public class MyAuthProvider : AuthProvider, IAuthWithRequest
{
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
return session.IsAuthenticated;
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
throw new NotImplementedException("Authenticate() should not be called directly");
}
public void PreAuthenticate(IRequest req, IResponse res)
{
//Do any Auth validation...
//populate the Session in the Request to Authenticate this user
req.Items[Keywords.Session] = new AuthUserSession {
UserName = ...,
Email = ...,
//populate other fields
IsAuthenticated = true,
};
}
}
Then to register your custom Auth Provider add it to your AuthFeature plugin in AppHost.Configure(), e.g:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new MyAuthProvider (),
}));