I am coding a C# web service using web API 2 where some of the CRUD actions need to have some security based off of a user's subscription.
Each user has a unique string token as part of their subscription.
Before I write my code for the webservice security, can someone please inform me if the following is a safe and efficient technique, and if not, why this is not safe and efficient and some other ways to accomplish this task.
I am thinking as part of each webservice request, a string parameter with the user's token is included. This token is then used to ensure that the user making the request owns the object.
Here is an example of a current function with no security:
[System.Web.Http.HttpGet]
[Route("Getdata/{id:int}")]
[ResponseType(typeof(Data))]
public async Task<IHttpActionResult> GetData(int id)
{
Data data = await dbSetService.Get(id);
if (data == null)
{
return NotFound();
}
return Ok(data);
}
Here is an example of a function with the token based security:
[System.Web.Http.HttpGet]
[Route("Getdata/{string:token},{id:int}")]
[ResponseType(typeof(Data))]
public async Task<IHttpActionResult> GetData(string token, int id)
{
Data data = await dbSetService.Get(id);
if (data == null)
{
return NotFound();
}
//Check if the owner of the data object has the correct token, and the object is returned if the token is correct
return Ok(data);
}
The webservice can be accessed by anyone, from any application.
The token itself is not the problem, but having the token in the GET as part of the querystring will make it readable for everybody in between the client and the server. Even if you use https. It's safer to add the token in the header of the http request (e.g. using the Authorization header); the head will also be encrypted using https (unlike the request). If you don't plan to use https, then it does not really matter, I guess.
Furthermore, I would add a filter class (and add is as property of the controller), so that you don't need to do the check in each method. But for a simple controller with 3 actions it may be overkill.
Related
I have this case where I have an anonymous endpoint can work on it's own but if a user have already done authentication with the cookie I need to get from that authenticated cookie session the data about the authenticated user, but as I searched online I haven't found a solution to this and the User.Identity.IsAuthenticated is always false despite the user has the authenticated session cookie but on a Anonymous endpoint it's like being ignored so I can't figure out if the user data in the cookie.
Is there any way to accomplish this?
Example of the needed behaviour:
[HttpGet,AllowAnonymous]
public async Task<IActionResult> GetSomething()
{
if(User.Identity.IsAuthenticated){
//Get data from User.Claims and act upon it
} else {
//Get data without user logic
}
}
You still must use the Authorize attribute. The AllowAnonymous attribute serves to still allow access if the user isn't authorized.
[HttpGet,Authorize,AllowAnonymous]
public async Task<IActionResult> GetSomething()
I'm implementing a straight out of the box solution using IDserver4(2.3) targeting .netcore 2.2 that communicates with a FHIR client by calling:
Url/api/openid/openapp?launch=12345t6u-34o4-2r2y-0646-gj6g123456t5&iss=myservice&launchOrganization=tilt
with some HL7 simulated scopes etc. The flow is okay all the way to the token endpoint serving access and id tokens using the quickstart on an IIS with certificates and all the bezels.
My problem lies in that the client requires a parameter to be passed to the external client pointing to a file or something on the server where I have some test patient data stored/or served as Json.
Any competent way to pass a parameter with the body or the header for example? And do you do it at the authorization or the authentication, or along with the tokens? Lets call it context. The service shut me down when i reach it. Says this on their side 'TypeError: Parameter "url" must be a string, not undefined'
Thanks in advance.
Got it using:
public class CustomClaimInjection : ICustomTokenRequestValidator
{
private readonly HttpContext _httpContext;
public CustomClaimInjection(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public Task ValidateAsync(CustomTokenRequestValidationContext context)
{
var client = context.Result.ValidatedRequest.Client;
//client.Claims.Add(new Claim("sub", sub)); // this will be [client_sub]
context.Result.CustomResponse = new Dictionary<string, object>
{
{"example-launchcontext", "https://url/" }
};
return Task.CompletedTask;
//return Task.FromResult(0);
}
}
I think I understand your problem now, and I think you would like a successful authentication to return additional information about where the patient's file is stored. I would store this in the token as a claim since it can be expressed as a statement about the subject (the user). This can be done in the registered (through dependency injection) implementation of the IProfileService. In the implementation of 'GetProfileDataAsync' you can set the issued claims using the 'ProfileDataRequestContext' parameter's property 'IssuedClaims'. These claims will be used to populate the id token which is what you should be looking to do.
I am using OAuth token based authentication in my web api based project.
If user is authenticated,an access token is generated as below.
{"access_token":"FFz_DC6zzEDD4mGOCk9172ijj3sGxCUWnk-tGanm9wGk76hMB8sHI8ImeWtdUKHHGNXv465ZSlbb-3fr_hr9DqUHc9Dm9OBI7XjJhdjdOpAGAGSFOpE0Y17LCEWTjCmEZotuf42Mpgl81ewoS7OlnH4b5w4PrtzJbIBpSAMoWObziL_U3mTkeFKvWrcWOfvlSCvhhBA9Dc3UTXv3HiHKWQk0T3-pvVy7ZuW2oac-IIuaq_GYaVkIZh7s9-YjX9KAL2Z9yfrPrVOQXZe_5OcNd7nS3tdT5odchEAiuWRYQ6t7Tfb2si4T6VdAe73OYefE0se1FeQsxbOiNaLyF8OwBqymEUzEG8tEHJ-cejVbhPw","token_type":"bearer","expires_in":1799,"as:client_id":"","user":"1","role":"1",".issued":"Thu, 16 Feb 2017 09:37:44 GMT",".expires":"Thu, 16 Feb 2017 10:07:44 GMT"}
Below is one of the api method.
[Authorize]
[HttpGet]
[Route("{userId}/{type}/")]
public IHttpResponse GetCustomerDetails(int userId, string type)
{
//my api stuff
}
I am using Postman for testing api. When I pass parameters as
http://localhost:50684/api/customer/1/gold
--along the access token in token in header--
It returns the desired json.
But if I use the same token & pass the customer id = 2,still it allows the access to the other customer(with id=2).
http://localhost:50684/api/customer/2/gold
--Access token in header--
It should NOT allow to access the resource to user with id=2 since the generated access token is valid for user with id =1.
How do I prevent this security breach?
Any help/suggestion highly appreciated.
Thanks
The problem is that you send the userId as a parameter which by itself is bad design.
The simple solution is to get the current user from the context instead
[Authorize]
[HttpGet]
[Route("{type}/")]
public IHttpResponse GetCustomerDetails(string type)
{
var user = RequestContext.Principal.Identity.Name;
//my api stuff
}
You can store user id and token in some storage(session, db).
And write own MVC authorization fileter like Authorize filter, which compare token with user id stored in storage.
Currently WebApi doesn't match the concept of user id and authenticated user's id. And it shouldn't, because the only thing you specify is the route of the controller's method with some parameter. You only require the user to be authenticated, by using "Authorize" attribute, but once the access is granted, no further validation is run. To make this method available to only specific subset of your users you could either write your own generic validation (i.e. in this case check against the claims of the user which can be accessed by "User" property within controller scope, or use some out-of-the-box external implementations of handling authentication.
I'm building an ASP.NET Web API and I'm currently implementing very basic user accounts. I have a User model which consists of just Email and Password fields, and I have a UserController class with the following action:
// POST: api/Users
[ResponseType(typeof(User))]
public IHttpActionResult PostUser(User user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Users.Add(user);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = user.Id }, user);
}
Everything works perfectly, except when I POST /api/Users I get the password field sent back in the response body:
{
"Id":2,
"Email":"dummy#test.com",
"Password":"$2a$12$jTACgOlm2eO/OYcV5wrDnO2dmsnbWVnsCRzX1WfQKGsY4sYvh16gm"
}
How do I go about making sure that sensitive fields such as user passwords never get output in a response? I'd prefer a method that does it on the model level so that I never accidentally forget to implement it in a controller.
One option would be to always use a Data Transfer Object (DTO) when communicating between a client and the server. The example given by this article from the Web Api team is very similar to your problem.
I would create a UserDTO which wouldn't contain any sensitive data that I wouldn't want to transfer between my clients and the server.
That is how most APIs work, take Facebook, for example, the User passed via this API call is not the User from their Domain, it is a representation of a User with just the needed information.
Using a DTO you could control exactly what gets transferred, lowering data size and preventing secure information from leaking.
UPDATE: If you go that road you'll probably want to use AutoMapper, it reduces the amount of Object to Object mapping you have to do considerably.
It's too much to just remove some values we need to create a separate model, better to clean the data before returning, say
db.SaveChanges();
user.Password = String.Empty;
user.anyproperty = null;
return CreatedAtRoute("DefaultApi", new { id = user.Id }, user);
John,
The CreatedAtRoute method is intended to return the URI of a newly created resource. For example, if you are creating a new product, you may see the return as api/products/1234. The method will also return the serialized object you provide as a third parameter. The method is not aware of the nature of the object you are returning therefore it does not recognize any of the fields as sensitive.
In your case, you may clear the password field from the user object or return a completely different object that does not include this field. You are not forced to return the same object you just created.
Best Regards,
Daniel
I am working on a single page application using AngularJS and ASP.NET Identity 2. I log the user in and the cookie is set; however, when I check the Identity of the user on the same request, it shows it as blank and IsAuthenticated is false. However, these are populated on subsequent requests. I was hoping to send back to the UI whether or not the user was logged in on the same request. Is this possible?
Code as requested (AngularJS makes AJAX post into WebAPI controller Login method)
[HttpPost]
[AllowAnonymous]
[Route("Login")]
public async Task<IHttpActionResult> Login(LoginModel loginModel)
{
var result = await _securityService.Login(loginModel.UserName, loginModel.Password);
if (!result)
{
ModelState.AddModelError("errorMessage", "Invalid username or password.");
return BadRequest(ModelState);
}
return Ok();
}
public async Task<bool> Login(string userName, string password, bool persistCookie = false)
{
var user = await _userManager.FindAsync(userName, password);
if (user != null)
await SignInAsync(user, persistCookie);
else
return false;
return true;
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
_authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
_authenticationManager.SignIn(new AuthenticationProperties() {IsPersistent = isPersistent}, await CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie));
}
public Task<ClaimsIdentity> CreateIdentity(ApplicationUser user, string authenticationType)
{
return _userManager.CreateIdentityAsync(user, authenticationType);
}
You won't get a signed in identity until the next request because the call to SignIn is what's causing a cookie to be set on the response. That cookie will turn into the identity on subsequent requests, but its too late to change your current request's identity.
When using Owin authentication, the AuthenticationManager.SignIn() method barely sends a message to the cookie handler to set a cookie when the cookie handler gets to handle the request after the Web API Controller (see my blog post Understanding the Owin External Authentication Pipeline for details).
But the Login method returns true if the login was successful and false if not, so you can use that information in the Login action to send back information. If you don't only want to know if the login succeeded or not, but also want the actual identity you can change Login() to return the user in case of successful login and null if failed.
I log the user in and the cookie is set; however, when I check the Identity of the user on the same request, it shows it as blank and IsAuthenticated is false.
This is just a lack of knowledge on your part about how the ASP.Net pipeline works.
There is a fairly large pipeline of events that occur. I'm pretty sure MVC runs in the ProcessRequest method. This method is after the AuthenticateRequest event and the PostAuthenticateRequest event. This means that the entire ASP.Net authentication framework can never be updated during the ProcessRequest method. This is why you'll see almost all system do a redirect afterwards, so that the next request has all the authentication (IIdentity, IPrincipal, IsAuthenticated, etc).
I was hoping to send back to the UI whether or not the user was logged in on the same request. Is this possible?
How could the code not be able to? The first request either authenticates them or not, whatever code is doing that knows if they are authenticated.
I was hoping to send back to the UI whether or not the user was logged in on the same request. Is this possible?
Yes. As said in other responses, you can.
I just want to cover the case when you are in the same request but outside the context where the SignIn took place.
Through Owin, you could use something like this extension method:
/// <summary>
/// Check if the user was authenticated in the current request, or in a previous one
/// </summary>
public static bool IsUserAuthenticated(this IOwinContext context)
{
if (context.Request.User.Identity.IsAuthenticated)
return true;
if (null != context.Authentication.AuthenticationResponseGrant && null != context.Authentication.AuthenticationResponseGrant.Identity)
{
return context.Authentication.AuthenticationResponseGrant.Identity.IsAuthenticated;
}
return false;
}