I've already implemented the basic Web API protection via IdentityServer4 based on this.
The demo is based on in-memory data. And most of tutorials are based on EF Core implementation for user data. As I searched there was a IUserService in IdentityServer3 which is now missing in version 4.
builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
builder.AddInMemoryUsers(Users.Get());
How can I retrieve my user data from an EF6 store?
In Startup.cs, do this
builder.Services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
builder.Services.AddTransient<IProfileService, ProfileService>();
Here is a sample of ResourceOwnerPasswordValidator and ProfileService
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private MyUserManager _myUserService { get; set; }
public ResourceOwnerPasswordValidator()
{
_myUserService = new MyUserManager();
}
public async Task<CustomGrantValidationResult> ValidateAsync(string userName, string password, ValidatedTokenRequest request)
{
var user = await _myUserService.FindByNameAsync(userName);
if (user != null && await _myUserService.CheckPasswordAsync(user, password))
{
return new CustomGrantValidationResult(user.EmailAddress, "password");
}
return new CustomGrantValidationResult("Invalid username or password");
}
}
public class ProfileService : IProfileService
{
MyUserManager _myUserManager;
public ProfileService()
{
_myUserManager = new MyUserManager();
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.FindFirst("sub")?.Value;
if (sub != null)
{
var user = await _myUserManager.FindByIdAsync(sub);
var cp = await getClaims(user);
var claims = cp.Claims;
if (context.AllClaimsRequested == false ||
(context.RequestedClaimTypes != null && context.RequestedClaimTypes.Any()))
{
claims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToArray().AsEnumerable();
}
context.IssuedClaims = claims;
}
}
public Task IsActiveAsync(IsActiveContext context)
{
return Task.FromResult(0);
}
private async Task<ClaimsPrincipal> getClaims(CustomerSite user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = await _myUserManager.GetUserIdAsync(user);
var userName = await _myUserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity();
id.AddClaim(new Claim(JwtClaimTypes.Id, userId));
id.AddClaim(new Claim(JwtClaimTypes.PreferredUserName, userName));
var roles = await _myUserManager.GetRolesAsync(user);
foreach (var roleName in roles)
{
id.AddClaim(new Claim(JwtClaimTypes.Role, roleName));
}
id.AddClaims(await _myUserManager.GetClaimsAsync(user));
return new ClaimsPrincipal(id);
}
}
Related
So, I have a scenario where I have implemented my own JWT authentication scheme and is the default authentication and challenge scheme in my Startup.cs:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
//code ommitted for brevity
})
.AddCookie("cookie")
.AddOpenIdConnect("facbook", async options =>
{
options.ResponseType = "code";
options.SignInScheme = "cookie";
//code ommitted for brevity
});
As you can see above that I have added AddOpenIdConnect and AddCookie for my external authentication. Now my question is that if I have a Redirect ActionMethod like this, how can return the Challenge scheme to point to my external one (facebook):
[HttpGet]
public async Task<IActionResult> Redirect()
{
var result = await HttpContext.AuthenticateAsync();
if (result.Succeeded)
{
return RedirectToAction("Index");
}
return Challenge("facebook");
}
This would also mean that my AuthenticateAsync would not work in this case since the default authentication scheme is pointing to JWT.
How can I add a this to my Challenge request and AuthenticateAsync method?
Thanks
To return the login page for your custom Identity Provider, you need to call the SignInManager.ConfigureExternalAuthenticationProperties() method. This method lets you define the redirectUrl and provider (you called your provider "facebook").
I have written it like this:
[Controller]
[Route("web/v2/[controller]")]
public class AccountController : Controller
{
private IAccountService accountService;
public AccountController(IAccountService accountService)
{
this.accountService = accountService;
}
// GET: web/Account/connect/{provider}
[AllowAnonymous]
[HttpGet("connect/{medium}/{provider}", Name = "web-v2-account-external-connect-challenge")]
public async Task<ActionResult> ExternalLogin([FromRoute]string medium, [FromRoute]string provider)
{
//var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Account", new { medium, provider });
var redirectUrl = Url.RouteUrl("web-v2-account-external-connect-callback", new { medium, provider });
var properties = await accountService.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
// GET: web/Account/connect/{provider}/callback
[HttpGet("connect/{medium}/{provider}/callback", Name = "web-v2-account-external-connect-callback")]
public async Task<ActionResult> ExternalLoginCallback([FromRoute]string medium, [FromRoute]string provider)
{
try
{
var login_result = await accountService.PerfromExternalLogin();
if (login_result.Status)
{
var model = new LoginResultVM
{
Status = true,
Medium = medium,
Platform = login_result.Platform
};
return View(model);
}
else
{
var model = new LoginResultVM
{
Status = false,
Medium = medium,
Platform = login_result.Platform,
Error = login_result.Error,
ErrorDescription = login_result.ErrorDescription
};
return View(model);
}
}
catch (OtherAccountException otherAccountEx)
{
var model = new LoginResultVM
{
Status = false,
Medium = medium,
Platform = provider,
Error = "Could not login",
ErrorDescription = otherAccountEx.Message
};
return View(model);
}
catch (Exception ex)
{
var model = new LoginResultVM
{
Status = false,
Medium = medium,
Platform = provider,
Error = "Could not login",
ErrorDescription = "There was an error with your social login"
};
return View(model);
}
}
}
While my AccountRepository looks like this:
internal interface IAccountRepository
{
...
}
internal class AccountRepository : IAccountRepository
{
private MintPlayerContext mintplayer_context;
private UserManager<Entities.User> user_manager;
private SignInManager<Entities.User> signin_manager;
private JwtIssuerOptions jwtIssuerOptions;
public AccountRepository(UserManager<Entities.User> user_manager, SignInManager<Entities.User> signin_manager, MintPlayerContext mintplayer_context, IOptions<JwtIssuerOptions> jwtIssuerOptions)
{
this.user_manager = user_manager;
this.signin_manager = signin_manager;
this.mintplayer_context = mintplayer_context;
this.jwtIssuerOptions = jwtIssuerOptions.Value;
}
public async Task<Tuple<User, string>> Register(User user, string password)
{
...
}
public async Task<LoginResult> LocalLogin(string email, string password, bool createCookie)
{
...
}
public async Task<IEnumerable<AuthenticationScheme>> GetExternalLoginProviders()
{
var providers = await signin_manager.GetExternalAuthenticationSchemesAsync();
return providers.ToList();
}
public Task<AuthenticationProperties> ConfigureExternalAuthenticationProperties(string provider, string redirectUrl)
{
var properties = signin_manager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Task.FromResult(properties);
}
public async Task<LoginResult> PerfromExternalLogin()
{
var info = await signin_manager.GetExternalLoginInfoAsync();
if (info == null)
throw new UnauthorizedAccessException();
var user = await user_manager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
if (user == null)
{
string username = info.Principal.FindFirstValue(ClaimTypes.Name);
string email = info.Principal.FindFirstValue(ClaimTypes.Email);
var new_user = new Entities.User
{
UserName = username,
Email = email,
PictureUrl = null
};
var id_result = await user_manager.CreateAsync(new_user);
if (id_result.Succeeded)
{
user = new_user;
}
else
{
// User creation failed, probably because the email address is already present in the database
if (id_result.Errors.Any(e => e.Code == "DuplicateEmail"))
{
var existing = await user_manager.FindByEmailAsync(email);
var existing_logins = await user_manager.GetLoginsAsync(existing);
if (existing_logins.Any())
{
throw new OtherAccountException(existing_logins);
}
else
{
throw new Exception("Could not create account from social profile");
}
}
else
{
throw new Exception("Could not create account from social profile");
}
}
await user_manager.AddLoginAsync(user, new UserLoginInfo(info.LoginProvider, info.ProviderKey, info.ProviderDisplayName));
}
await signin_manager.SignInAsync(user, true);
return new LoginResult
{
Status = true,
Platform = info.LoginProvider,
User = ToDto(user)
};
}
public async Task<IEnumerable<UserLoginInfo>> GetExternalLogins(ClaimsPrincipal userProperty)
{
...
}
public async Task AddExternalLogin(ClaimsPrincipal userProperty)
{
...
}
public async Task RemoveExternalLogin(ClaimsPrincipal userProperty, string provider)
{
...
}
public async Task<User> GetCurrentUser(ClaimsPrincipal userProperty)
{
var user = await user_manager.GetUserAsync(userProperty);
return ToDto(user);
}
public async Task Logout()
{
await signin_manager.SignOutAsync();
}
#region Helper methods
private string CreateToken(Entities.User user)
{
...
}
#endregion
#region Conversion methods
internal static Entities.User ToEntity(User user)
{
...
}
internal static User ToDto(Entities.User user)
{
...
}
#endregion
}
I create project for test authentication in ASP.Net Core Web API with using JWT tokens. I implemented the basic functionality for working with accounts, but I ran into some problems.
UsersController:
[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly IAuthenticationService _authenticationService;
public UsersController(
IUserService userService,
IAuthenticationService authenticationService)
{
_userService = userService;
_authenticationService = authenticationService;
}
// PUT: users/5
[HttpPut("{id}")]
public async Task<ActionResult> PutUser(int id, [FromBody]UpdateUserModel model)
{
try
{
var user = await _userService.UpdateAsync(model, id);
return Ok();
}
catch(Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
// POST : users/authenticate
[AllowAnonymous]
[HttpPost("authenticate")]
public async Task<ActionResult<User>> Authenticate([FromBody] AuthenticateUserModel model)
{
var user = await _authenticationService.AuthenticateAsync(model);
if (user == null)
return BadRequest(new { message = "Login or password is incorrect" });
return Ok(user);
}
}
AuthenticationService:
public async Task<User> AuthenticateAsync(AuthenticateUserModel model)
{
var users = await _context.Users.ToListAsync();
var user = users.SingleOrDefault(x => x.Login == model.Login && x.Password == model.Password);
if (user == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString()),
new Claim(ClaimTypes.Role, user.Role)
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
return user.WithoutPassword();
}
It turns out that after authorization, any user can edit the data of another user if we specify a different id in the client who will send requests. Is it possible to somehow limit the actions thanks to the token or how is it better to do this?
You should't trust the submitted data from the user. you should set UserId in payload data like what you did yourself
new Claim(ClaimTypes.Name, user.Id.ToString()),
and when user edit the data get user id from JWT like this
public int GetCurrentUserId()
{
var claimsIdentity = _contextAccessor.HttpContext.User.Identity as ClaimsIdentity;
var userDataClaim = claimsIdentity?.FindFirst(ClaimTypes.Name);
var userId = userDataClaim?.Value;
return string.IsNullOrWhiteSpace(userId) ? 0 : int.Parse(userId);
}
or
int userId = Convert.ToInt32((User.Identity as ClaimsIdentity).FindFirst(ClaimTypes.Name).Value);
and finally
[HttpPut("PutUser")]
public async Task<ActionResult> PutUser([FromBody]UpdateUserModel model)
{
try
{
int userId = Convert.ToInt32((User.Identity as ClaimsIdentity).FindFirst(ClaimTypes.Name).Value);
var user = await _userService.UpdateAsync(model, userId);
return Ok();
}
catch (Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
In the out of the box ASP.NET WEB API oAuth implementation after a new user calls:
GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
user is redirected to external log in (in my case Facebook) resulting in a token that they use for registration (out of the box code bellow)
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal([FromBody]RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
IdentityUser user = new IdentityUser
{
UserName = model.UserName
};
user.Logins.Add(new IdentityUserLogin
{
LoginProvider = externalLogin.LoginProvider,
ProviderKey = externalLogin.ProviderKey
});
IdentityResult result = await UserManager.CreateAsync(user);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
During RegisterExternal I want to populate another database using the data on their Facebook (first name, last name, email, friends, ext..)
The Bearer token I am getting during registration can not simply be called as such:
var accessToken = "token from header";
var client = new FacebookClient(accessToken);
So from what I understand I need to modify Startup.Auth with claims for this data as i have done by adding:
var facebookProvider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
// Add the email id to the claim
context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email));
return Task.FromResult(0);
}
};
var options = new FacebookAuthenticationOptions()
{
AppId = "xxxxxxxxxxxxxxxxx",
AppSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
Provider = facebookProvider
};
options.Scope.Add("email");
options.Scope.Add("user_friends");
options.Scope.Add("public_profile");
app.UseFacebookAuthentication(options);
But then how do I go about fetching that data in my RegisterExternal method?
I had the same problem (I think) - the problem was that FB the OAuth infrastructure was only filling the basic data and I wanted a bit more.
After doing some digging into the source code of ASP.NET identity, I turned out with the following:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = "",
AppSecret = "",
Scope = { "public_profile", "email", "user_birthday", "user_location" },
Provider = new FacebookAuthProvider(),
UserInformationEndpoint = "https://graph.facebook.com/v2.5/me?fields=id,name,email,first_name,last_name,location,birthday,picture",
});
and the important part here is the custom provider:
private class FacebookAuthProvider : FacebookAuthenticationProvider
{
/// <summary>
/// Invoked whenever Facebook succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="T:System.Security.Claims.ClaimsIdentity" />.</param>
/// <returns>A <see cref="T:System.Threading.Tasks.Task" /> representing the completed operation.</returns>
public override Task Authenticated(FacebookAuthenticatedContext context)
{
TryParseProperty(context, "first_name", Claims.FirstName);
TryParseProperty(context, "last_name", Claims.LastName);
TryParseProperty(context, "picture.data.url", Claims.PhotoUrl);
return base.Authenticated(context);
}
private void TryParseProperty(FacebookAuthenticatedContext context, string name, string targetName)
{
var value = context.User.SelectToken(name);
if (value != null)
{
context.Identity.AddClaim(targetName, value.ToString());
}
}
}
This basically puts all the data in the claim and can be retrieved anywhere else the same way.
The external provider, in this case Facebook, will populate the Claims and these can be accessed in your callback method in LoginInfo.
Here's the code for reading the Facebook Access token:
var accessToken = loginInfo.ExternalIdentity.Claims.FirstOrDefault(x => x.Type == Constants.FacebookAccessToken).Value;
If you set a breakpoint there you'll be able to see what else is returned by Facebook.
John Mc really pointed me in the right direction, here is a more full solution.
// POST api/Account/RegisterExternalToken
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternalToken")]
public async Task<IHttpActionResult> RegisterExternalToken()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
var facebookToken = externalLogin.Token;
And then in the claims (this is the key part) as John's pointed out:
private class ExternalLoginData
{
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
public string UserName { get; set; }
public string Token { get; set; }
public IList<Claim> GetClaims()
{
IList<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));
if (UserName != null)
{
claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
}
if (Token != null)
{
claims.Add(new Claim("FacebookAccessToken", Token, null, LoginProvider));
}
return claims;
}
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
|| String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name),
Token = identity.Claims.FirstOrDefault(x => x.Type.Contains("FacebookAccessToken")).Value
};
}
}
I am having a problem with "cache" in asp .net identity, when I change password, name, any claim, I must restart the application for validate the changes.
I have this in SecurityContext
public class SecurityContext : IdentityDbContext<IdentityUser>
{
public SecurityContext()
: base("Db")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("security");
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>()
.ToTable("_Users");
modelBuilder.Entity<IdentityRole>()
.ToTable("_Roles");
modelBuilder.Entity<IdentityUserRole>()
.ToTable("_UsersRoles");
modelBuilder.Entity<IdentityUserClaim>()
.ToTable("_UsersClaims");
modelBuilder.Entity<IdentityUserLogin>()
.ToTable("_UsersLogins");
}
}
Login:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _PublicClientId;
private readonly Func<UserManager<IdentityUser>> _UserManagerFactory;
private readonly Func<RoleManager<IdentityRole>> _RoleManagerFactory;
#region Constructors
public ApplicationOAuthProvider(string publicClientId,
Func<UserManager<IdentityUser>> userManagerFactory,
Func<RoleManager<IdentityRole>> roleManagerFactory
)
{
if (publicClientId == null)
throw new ArgumentNullException("publicClientId");
_PublicClientId = publicClientId;
if (userManagerFactory == null)
throw new ArgumentNullException("userManagerFactory");
_UserManagerFactory = userManagerFactory;
if (roleManagerFactory == null)
throw new ArgumentNullException("roleManagerFactory");
_RoleManagerFactory = roleManagerFactory;
}
#endregion Constructors
#region GrantResourceOwnerCredentials
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (var userManager = _UserManagerFactory())
{
using (var roleManager = _RoleManagerFactory())
{
var user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// Start Login success
var oAuthIdentity = await userManager.CreateIdentityAsync(user, context.Options.AuthenticationType);
var cookiesIdentity = await userManager.CreateIdentityAsync(user, CookieAuthenticationDefaults.AuthenticationType);
// Claims
cookiesIdentity.AddClaim(new Claim(XpClaimTypes.Application, _SessionData.ApplicationName));
// Properties
var properties = CreateProperties(user, roleManager);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
// End Login success
}
}
}
#endregion GrantResourceOwnerCredentials
}
obviating others methods
For example the method for changePassword:
#region Password
[HttpPut]
[Authorize(Roles = AccountRoles.Superadministrador + "," + AccountRoles.Administrador)]
public async Task<IHttpActionResult> Password(SetPasswordBindingModel model)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var identity = await UserManager.FindByNameAsync((Thread.CurrentPrincipal.Identity as ClaimsIdentity).Name);
var user = await UserManager.FindByIdAsync(model.Id);
if (!(
(identity.Roles.Any(x => x.Role.Name == AccountRoles.Superadministrador) && user.Roles.Any(x => x.Role.Name == AccountRoles.Administrador)) ||
(identity.Roles.Any(x => x.Role.Name == AccountRoles.Administrador) && user.Roles.Any(x => x.Role.Name == AccountRoles.Usuario))
))
throw new AuthenticationException();
// Delete password
{
var result = await UserManager.RemovePasswordAsync(model.Id);
var errorResult = GetErrorResult(result);
if (errorResult != null)
return errorResult;
}
// Add password
{
var result = await UserManager.AddPasswordAsync(model.Id, model.Password);
var errorResult = GetErrorResult(result);
if (errorResult != null)
return errorResult;
}
return Ok();
}
#endregion Password
There are the steps I followed:
Login application
Change the password
Logout application
Login with the new password (in table is changed, is correctly the change)
Error with password
Login with older password (the old password in table is not exists)
Login successful
Restart application
The new password now is valid
The same problem is occurred when I change any value in BBDD of asp .net identity
Any Idea please?
Thanks!!
If I recall correctly I add the same issue because one of the contexts was being persisted and the other recreated on every call.
If you check one will not have the correct value from the DB, probably ApplicationOAuthProvider.
Try recreating the context for every call on the ApplicationOAuthProvider.
I cannot seem to login when I call -- api/values. the client-end throws "Authorization has been denied for this request." message.
I tried debugging the basicAuthHandler class but it does not seem to be crashing anywhere, so I am little stuck and how can I pin point the issue.
could it be my validate method or constructor in my global.aspx?
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
//[Inject]
//public iUser Repository { get; set; }
// private readonly iUser Repository;
private readonly iUser Repository = new User();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter))
{
api_login parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
IPrincipal principal;
if (TryGetPrincipal(parsedCredentials.username, parsedCredentials.password, out principal))
{
Thread.CurrentPrincipal = principal;
//request.GetRequestContext().Principal = principal;
}
}
}
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized && !response.Headers.Contains(BasicAuthResponseHeader))
{
response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
return response;
});
}
private api_login ParseAuthorizationHeader(string authHeader)
{
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1])) return null;
return new api_login()
{
username = credentials[0],
password = credentials[1],
};
}
private bool TryGetPrincipal(string userName, string password, out IPrincipal principal)
{
// this is the method that authenticates against my repository (in this case, hard coded)
// you can replace this with whatever logic you'd use, but proper separation would put the
// data access in a repository or separate layer/library.
api_login user = Repository.Validate2(userName, password);
if (user.username != null)
{
// once the user is verified, assign it to an IPrincipal with the identity name and applicable roles
principal = new GenericPrincipal(new GenericIdentity(user.username), null);
}
principal = null;
return false;
}
}
}
global.aspx:
GlobalConfiguration.Configuration.MessageHandlers.Add(new BasicAuthMessageHandler());
Any help would be very much appreciated.
Thank you.
I think you didn't handle the response correctly in your code, I created a MessageHandler for Basic Authentication base on your code, hope it'll give you an good idea (I didn't test it), see below:
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
//[Inject]
//public iUser Repository { get; set; }
// private readonly iUser Repository;
private readonly iUser Repository = new User();
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue == null || authValue.Scheme != BasicAuthResponseHeaderValue)
{
return Unauthorized(request);
}
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authValue.Parameter)).Split(new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]))
{
return Unauthorized(request);
}
api_login user = Repository.Validate2(credentials[0], credentials[1]);
if (user == null)
{
return Unauthorized(request);
}
IPrincipal principal = new GenericPrincipal(new GenericIdentity(user.username, BasicAuthResponseHeaderValue), null);
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
return base.SendAsync(request, cancellationToken);
}
private Task<HttpResponseMessage> Unauthorized(HttpRequestMessage request)
{
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
var task = new TaskCompletionSource<HttpResponseMessage>();
task.SetResult(response);
return task.Task;
}
}