The web api control, UserControl, has two methods, RetrieveUserID which needs Basic Authorization check
[HttpGet]
[Route("RetrieveUserID/{strUsername}")]
[Authorize]
public string RetrieveUserID(string strUsername)
{
//retrieve userID and return it
......
return strUserID;
}
Another method, FailAuthenticationReason, is used if fail to retrieve userID, it returns the detail fail info such as wrong username, wrong password, account is locked out, etc. which doesn't need to do any authentication check
[HttpGet]
[Route("FailAuthenticationReason/{strUsername}")]
public string FailAuthenticationReason(string strUsername)
{
//retrieve detail failed reason
......
return strFailedReason;
}//End of
It works well when I use a browser to check them. But when I use it in my app, after I provide the authorization header and fail to retrieve userID because of incorrect username and/or password, it also do the authorization check when it call FailAuthenticationReason
var authData = string.Format("{0}:{1}", entUsername.Text,entPassword.Text);
var authHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(authData));
App.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authHeaderValue);
var uri = new Uri(string.Format(App.strWebAPIURI + "/RetrieveUserID/{0}", entUsername.Text));
try
{
var response = await App.httpClient.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
......
}
else
{
//Fail to pass authorization
uri = new Uri(string.Format(App.strWebAPIURI + "/FailAuthenticationReason/{0}", entUsername.Text));
response = await App.httpClient.GetAsync(uri);
......
}
How can the program call FailAuthenticationReason without the authorization check?
Related
i have already defined API which display the full information of student when email and password is provided . But when i want to use that API from another project its not able to pass the values of email and password.
[Route("CallApiGet")]
[HttpGet]
public async Task<IHttpActionResult> CallAPIGET()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://localhost:44376/");
var GetData = new
{
Email = "abc#gmail.com",
Password = "dhdsi#hc"
};
var content = new StringContent(JsonConvert.SerializeObject(GetData), Encoding.UTF8, "application/json");
using (HttpResponseMessage response = await client.GetAsync("api/CustomerController",content))
{
var responseContent = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
return Ok(responseContent);
}
}
}
I have tried passing the value of email and password in variable content but getting error.
A httpget has no body ( content ) so if you use a get then you should use query parameters.
The get URL should be more like:
ApiUrl/controller?email=xxxx&password=yyyy
Your endpoint presumably has these as parameters.
Having said that, a httpget is no secure. It's just a plain string with a password in it.
I suggest you consider changing this endpoint to a post and then you can send parameters in the body. Assuming this is Https they will then be encrypted and rather more secure.
I have looking into code of a web application where token is generated and injected into url instead of auth cookie for each request token is passed with url to accessed secured action. The web application is using token instead of auth cookie. Token life is one day.
This is a sample url
http://localhost:48000/ACX/Default/Login?token=8kzRLdW8lQVIS0MrtlqdZJbmz9p22l33u1wspGOmLgCgEy2MG5XZ0JG1ovVZGiNX7KpAfBVn3
of that web application where token is passing through url.
This code is generating the token which would valid up to 24 hours:
public IActionResult Login([FromBody]LoginModel user)
{
if (user == null)
{
return BadRequest("Invalid request");
}
if (user.UserName == "johncitizen" && user.Password == "abc#123")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("KeyForSignInSecret#1234"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokeOptions = new JwtSecurityToken(
issuer: "http://localhost:2000",
audience: "http://localhost:2000",
claims: new List<Claim>(),
expires: DateTime.Now.AddMinutes(1440), // valid till 24 hours
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
return Ok(new { Token = tokenString });
}
else
{
return Unauthorized();
}
}
My question is: when token is passed through the URL, then any other person can get the URL and impersonate the user. I guess passing token through URL is not secure.
What can we do as a result token would be secure passing through URL? I want to change flow bit in such a way that if another user copy and paste the same URL, then he will not be able to access protected resource. So how to achieve and secure long life token?
Please guide me with approach in details. Thanks
You can try to use IDataProtectionProvider protect your data.
Configure in your program.cs:
builder.Services.AddDataProtection();
Or you can also specify algorithms (using Microsoft.AspNetCore.DataProtection) used for encryption and validation, like this:
builder.Services.AddDataProtection()
.UseCryptographicAlgorithms(new AuthenticatedEncryptionSettings()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
Then inject IDataProtectionProvider into your controller, Create your own key And create Create encryption and decryption methods:
public class HomeController : Controller
{
//Inject IDataProtectionProvider into your controller
private readonly IDataProtectionProvider _dataProtectionProvider;
//create your own key
private const string Key = "my-very-long-key-of-no-exact-size";
public HomeController(ILogger<HomeController> logger, IDataProtectionProvider dataProtectionProvider)
{
_logger = logger;
_dataProtectionProvider = dataProtectionProvider;
}
public IActionResult Login([FromBody]LoginModel user)
{
//...............
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
var keytoken = Encrypt(tokenString);
return Ok(new { Token = keytoken });
//...............
}
//encryption methods
public string Encrypt(string input)
{
var protector = _dataProtectionProvider.CreateProtector(Key);
return protector.Protect(input);
}
//decryption methods
public string Decrypt(string output)
{
var protector = _dataProtectionProvider.CreateProtector(Key);
return protector.Unprotect(output);
}
}
Now, Token has been encrypted.
Even if someone else gets your toke, Because they don't have the key, So they can't decrypt the token.
This is my scenario:
I’m building a ASP Net MVC Application that communicates with a WebApi. Sometimes, I need a WebApiClient to get some data from WebApi controllers. In order to authorize my WebApiClient with current logged user credentials, I want to use access tokens with OAuth2.
My idea was to generate a token during login and add generated token in a Claim. In order to do so, in AccountController:
Login(LoginViewModel model){
[...]
var signinm = HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
var ident = signinm.CreateUserIdentity(user);
//Generate token
BearerToken token = await (new TokenApi()).GetToken(model.UserName, model.Password);
//Store generated token in a claim
ident.AddClaim(new Claim(WebApiClient.API_TOKEN_KEY,
Newtonsoft.Json.JsonConvert.SerializeObject(token)));
[…]
//Finally sign in
HttpContext.GetOwinContext().Authentication.SignIn(authProp, ident);
}
My main questions are regarding the method GrantResourceOwnerCredentials in
ApplicationOAuthProvider:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
[...]
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity identity = await userManager.CreateIdentityAsync(
user,
DefaultAuthenticationTypes.ExternalBearer);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
context.Validated(ticket);
}
[...]
}
Do I have to generate this claimsIdentity and with which type of DefaultAuthenticationTypes: Cookie or ExternalBearer? Both seem to work.
Since HttpContext.GetOwinContext().Authentication.SignIn(...) is already performed in Login, it is not necessary to signIn also in GrantResourceOwnerCredentials, am I wrong?
Thank you for your help.
I'm working on writing fully customized ASP.NET Identity for my WebAPi.
I have rewritten my own derived OAuthAuthorizationServerProvider in this way:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Check User availability ...
//ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
// if i couldn't found user in my DataBase ...
//if (user == null)
//{
//context.SetError("invalid_grant", "The user name or password is incorrect.");
// return;
//}
context.Validated();
}
}
GrantResourceOwnerCredentials just returns an invalid_grant error for each calls. i want to handle it but, i don't know how.
ValidateClientAuthentication is where you would do your authentication checks and this is where you throw errors if anything doesn't match.
move your code there and do the checks before you call context.Validated(). You only call the Validate method once you make sure everything is validated correctly.
here is an example of such an implementation I did a while back:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
//first try to get the client details from the Authorization Basic header
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
//no details in the Authorization Header so try to find matching post values
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("client_not_authorized", "invalid client details");
return Task.FromResult<object>(null);
}
var dataLayer = new RepoManager(new DataLayerDapper()).DataLayer;
var audienceDto = dataLayer.GetAudience(clientId);
if (audienceDto == null || !clientSecret.Equals(audienceDto.Secret))
{
context.SetError("unauthorized_client", "unauthorized client");
return Task.FromResult<object>(null);
}
context.Validated();
return Task.FromResult<object>(null);
}
Notice how the checks happen in order and certain errors are raised with some appropriate errors.
This code takes a client id and client secret from an authorization header but you can easily drop all that and replace it with your own checks and database calls.
The important part is that this is where you deal with stuff like this and this is where you set the errors so your clients know what's going on.
GrantResourceOwnerCredentials this is where you get once the call is properly authenticated, at which point you can start creating tokens, adding claims and creating the authentication ticket. This method does not get hit if the previous one fails to authenticate the request.
Here is a working example:
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("clientID", context.ClientId));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"audience", context.ClientId
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
Now, if you get an invalid grant error that usually happens because you either didn't set up the grant_type in your initial call or you set up the wrong value.
in my case I had to setup this:
"grant_type", "password"
I have an web front end calling an ASP Web Api 2 backend. Authentication is managed with ASP Identity. For some of the controllers I'm creating I need to know the user making the call. I don't want to have to create some weird model to pass in including the user's identity (which I don't even store in the client).
All calls to the API are authorized using a bearer token, my thought is the controller should be able to determine the user context based on this but I do not know how to implement. I have searched but I don't know what I'm searching for exactly and haven't found anything relevant. I'm going for something like...
public async Task<IHttpActionResult> Post(ApplicationIdentity identity, WalkthroughModel data)
Update
I found the below which looked very promising... but the value is always null! My controller inherits from ApiController and has an Authorize header.
var userid = User.Identity.GetUserId();
Update 2
I have also tried all of the solutions in Get the current user, within an ApiController action, without passing the userID as a parameter but none work. No matter what I am getting an Identity that is valid and auth'd, but has a null UserID
Update 3
Here's where I'm at now.
[Authorize]
[Route("Email")]
public async Task<IHttpActionResult> Get()
{
var testa = User.Identity.GetType();
var testb = User.Identity.GetUserId();
var testc = User.Identity.AuthenticationType;
var testd = User.Identity.IsAuthenticated;
return Ok();
}
testa = Name: ClaimsIdentity,
testb = null,
testc = Bearer,
testd = true
The user is obviously authenticated but I am unable to retrieve their userID.
Update 4
I found an answer, but I'm really unhappy with it...
ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
string username = identity.Claims.First().Value;
That gets me the username without any db calls but it seems very janky and a pain to support in the future. Would love if anyone had a better answer.
What if I need to change what claims are issued down the road? Plus any time I actually need the user's id I have to make a db call to convert username to ID
A common approach is to create a base class for your ApiControllers and take advantage of the ApplicationUserManager to retrieve the information you need. With this approach, you can keep the logic for accessing the user's information in one location and reuse it across your controllers.
public class BaseApiController : ApiController
{
private ApplicationUser _member;
public ApplicationUserManager UserManager
{
get { return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
}
public string UserIdentityId
{
get
{
var user = UserManager.FindByName(User.Identity.Name);
return user.Id;
}
}
public ApplicationUser UserRecord
{
get
{
if (_member != null)
{
return _member ;
}
_member = UserManager.FindByEmail(Thread.CurrentPrincipal.Identity.Name);
return _member ;
}
set { _member = value; }
}
}
I use a custom user authentication (I dont use AspIdentity because my existing user table fields was far different from IdentityUser properties) and create ClaimsIdentity passing my table UserID and UserName to validate my bearer token on API calls.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
User user;
try
{
var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
_service = scope.Resolve<IUserService>();
user = await _service.FindUserAsync(context.UserName);
if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
catch (Exception ex)
{
context.SetError("invalid_grant", ex.Message);
return;
}
var properties = new Dictionary<string, string>()
{
{ ClaimTypes.NameIdentifier, user.UserID.ToString() },
{ ClaimTypes.Name, context.UserName }
};
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
properties.ToList().ForEach(c => identity.AddClaim(new Claim(c.Key, c.Value)));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(properties));
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(identity);
}
And how I use the ClaimsIdentity to retrieve my User table details on User ApiController Details call.
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("Details")]
public async Task<IHttpActionResult> Details()
{
var user = await _service.GetAsync(RequestContext.Principal.Identity.GetUserId<int>());
var basicDetails = Mapper.Map<User, BasicUserModel>(user);
return Ok(basicDetails);
}
Notice the
ClaimTypes.NameIdentifier = GetUserId() and ClaimTypes.Name = GetUserName()