In my ASP.NET MVC 5 application I need to use custom Authentication. Basically a custom library on which I call a method and which returns an object that contains information about the user.
I've created a new MVC 5 application and selected the "No Authentication" option. Then I've added an Http Module which currently looks like this:
private void Context_AuthenticateRequest(object sender, EventArgs e)
{
// Make the call to authenticate.
// This returns an object with user information.
AuthResult result = new AuthLib().SignOn();
// Inspect the returned object and create a list claims.
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, result.Username),
new Claim(ClaimTypes.GivenName, result.Name)
}
claims.AddRange(result.Groups.Select(g => new Claim(ClaimType.Role, g));
// Create principal and attach to context
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Sso");
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
private void Context_PostAuthenticateRequest(object sender, EventArgs e)
{
var principal = ClaimsPrincipal.Current;
ClaimsAuthenticationManager transformer = FederatedAuthentication.SessionAuthenticationModule.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
transformer.Authenticate(string.Empty, principal);
}
My claimstransformer looks like this:
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
ClaimsPrincipal newPrincipal = CreateApplicationPrincipal(incomingPrincipal);
EstablishSession(newPrincipal);
return newPrincipal;
}
private void EstablishSession(ClaimsPrincipal newPrincipal)
{
var sessionToken = new SessionSecurityToken(newPrincipal, TimeSpan.FromHours(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
}
private ClaimsPrincipal CreateApplicationPrincipal(ClaimsPrincipal incomingPrincipal)
{
// Convert AD group to known role in our application.
string group = incomingPrincipal.FindFirst(ClaimTypes.Role).Value;
string role = new ADGroupToRoleConverter().ConvertADGroupToRole(group);
// Add claims for group.
// These would be loaded from a db.
List<Claim> claims = new ClaimDb().GetClaimsForRole(role);
// Just copy the claims for id and given name.
claims.Add(incomingPrincipal.FindFirst(ClaimTypes.NameIdentifier));
claims.Add(incomingPrincipal.FindFirst(ClaimTypes.GivenName));
return new ClaimsPrincipal(new ClaimsIdentity(claims, "MyApp"));
}
The main issue that I'm facing is that the authentication step is called for every request even though a session exists. How can I detect that a session exists and just load the session instead of going through the entire authentication process.
Another issue is that the call to the authentication library might take a while. I guess ideally it should also be moved to the claims transformer?
Any ideas to improve this code further are also very much appreciated.
Please let me know if something is not clear or if I need to provide more detailed information.
It seems to me that you do not provide authentication information with each request after the authentication. Can you verify that you have some session cookie or authentication header sent with each request after the authentication happens?
Related
In my project, I am using token-based authentication and after a successful login, I store some user-specific values in user's token and to do this I have used Claims.
Below is the code I am using to store claims after login:
User user = new UserManager().GetUser(UserName, Password);
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.FullName),
new Claim(ClaimTypes.Email, user.Email),
new Claim("IsLocked", Convert.ToString(user.IsLocked))
};
AuthenticationProperties properties = CreateProperties(context.UserName);
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(claims, Startup.OAuthOptions.AuthenticationType);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
As you can see in the above code that I have a claim to store IsLocked value of the user. As per the requirement, I need to prevent access of each API action from users whose account is locked. To do so, I have created a custom action filter and inside that, I use the value of IsLocked claim and thus prevent actions from being executed if user's claim value says that the user account is locked.
Below is the code of my custom action filter:
public class AllowActiveUsersAttribute : ActionFilterAttribute
{
public AllowActiveUsersAttribute()
{
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var identity = (ClaimsPrincipal)Thread.CurrentPrincipal;
if (Convert.ToBoolean(identity.Claims.Where(c => c.Type == "IsLocked").Select(c => c.Value).SingleOrDefault()))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
And then I use this custom Attribute on all Web API actions, like:
[AllowActiveUsers]
public async Task<IHttpActionResult> GetAccountDetails()
This code works perfectly and I get Unauthorized error when I log in with a locked account and then try to use any API endpoint.
In our system, we have some rules and breaking those rules can lock the users' account. When the account gets locked then the user shouldn't be able to access any API endpoint. So after the successful login (with an account that is not locked), if a user breaks any rule then his/her account should get locked immediately and after that he/she must not be able to use any API endpoints.
To do this, I added code to update the value of IsLocked claim and it successfully updates the claim value. But when I try to get the value of IsLocked claim in the custom action then I get the same old value instead of the new return value. Below is the code that I am using to update the claim value.
// check for the existing claim and remove it
var user = User as ClaimsPrincipal;
var identity = user.Identity as ClaimsIdentity;
var claim = (from c in user.Claims where c.Type == "IsLocked" select c).FirstOrDefault();
if (claim != null)
identity.RemoveClaim(claim);
// add new claim
identity.AddClaim(new Claim("IsLocked", Convert.ToString(true)));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
Can you please suggest how I can get the new value in the custom attribute so that if a user's account gets locked then from next requests, none of the API requests should be entertained?
That's cause you are still using the old/existing token which still don't have the updated value of IsLocked and thus the scenario. To resolve this, either the user has to start afresh by means of generating a new token which would have the updated value. Essentially a new access token has to be generated.
See this issue link for more understanding https://github.com/IdentityServer/IdentityServer3/issues/2783
I'm looking for a minimal example for a custom authentication writen in C# for asp.net core 2 based on for example API keys.
Mircosoft has a pretty good documentation about doing this with cookies, however this is not what I want. Since I want to use API keys (given by http-header, GET or Cookie, ...) I never make a call to HttpContext.SignInAsync and this is maybe the issue I can't find/google my way around.
I built an simple AuthenticationHandler (based on this - since I read that custom middlewares are not the way to go anymore) which looks something like this:
internal class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// parse cookies to find APIKEY
if(Context.Request.Cookies.ContainsKey("APIKEY"))
{
string APIKEY = Request.Cookies["APIKEY"];
// ... checking DB for APIKEY ...
// creating claims
var claims = new[]
{
new Claim( /* ... */ ),
// ...
};
var claimsIdentity = new ClaimsIdentity(claims);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
var ticket = new AuthenticationTicket(claimsPrincipal, new AuthenticationProperties(), "Custom Scheme");
return AuthenticateResult.Success(ticket); // this line gets called
}
return AuthenticateResult.NoResult();
}
}
But when I have an API endpoint with just the [Authorize] attribute the DenyAnonymousAuthorizationRequirement denies the request cause the user is not allowed (cause IsAuthenticated == false which is readonly, all claims are shown properly)
Change var claimsIdentity = new ClaimsIdentity(claims); into something like var claimsIdentity = new ClaimsIdentity(claims, "Password"); (of course, instead of "Password" use the AuthenticationType that best fits your case).
Similar question here: Why is my ClaimsIdentity IsAuthenticated always false (for web api Authorize filter)?
I have a custom attribute where I manually wanna check if a claims token is valid. How do I do that?
public class AuthorizeClaimsAttribute : AuthorizeAttribute {
protected override bool UserAuthorized(IPrincipal user) {
var cookie = HttpContext.Current.Request.Cookies.Get("bearerToken");
if (cookie != null) {
//Check if token is valid, how?
}
return false;
}
}
The token is created as follow:
var identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim("Username", model.Username));
identity.AddClaim(new Claim("IsAdmin", isAdmin.ToString()));
var properties = new AuthenticationProperties() {
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(Startup.OAuthOptions.AccessTokenExpireTimeSpan)
};
var ticket = new AuthenticationTicket(identity, properties);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Note: I cannot use the existing Authorize attribute. That´s why I need to check it manually.
One method is to store the token alongside its username somewhere in a persistant data structure
For example, when you create the Identity store the model.UserName & the accessToken in a a database of your choice.
Then, when you want to check your cookie you can re-open your database and query for it and take the appropriate action.
Also, adding that date in the database will also help you keep the size of it down resulting in faster searches, i.e. if your token only lasts for 3 months, delete the old ones as part of maintenance
I want to change the way the default bearer token system works.
I want to login to the webAPI providing the username, password, and mac address of the device. Like so.
Content-Type: application/x-www-form-urlencoded
username=test&password=P#ssword&grant_type=password&client_id=android&device_info=MAC_Address
I then want the API to provide me with a Refresh Token. This token will be valid for say 7 days and will allow for me to get a new access token. However in the refresh token I want to save / embed the security stamp of the users password in the token along with the extirpation date. This way I can check the security stamp when a new access token is requested. (solves password changing scenario)
My access token only needs to store the bare amount of information for it to work. I don't require that the access token store anything specific. I would like to keep it as small as possible. When it expires I will simply request a new access token using my refresh token.
Now I have tried to implement the above but have got my self heavily confused about what to implement where. Here's what i have got.
Step 1: The Startup.Auth.cs
//Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20)
};
Now in here I already have some questions. I want to have two providers, one which handles Refresh Tokens and one that handles Access Tokens. Which providers do I need to set? because I see there is also one called AccessTokenProvider = then what is Provider = for?
Step 2: The RereshTokenProvider. This is what I have so far:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
//Used to store all the refresh tokens
public static ConcurrentDictionary<string, AuthenticationTicket> RefreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString("N");
//copy properties and set the desired lifetime of refresh token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
};
//TODO: get mac address from the request headers??
//TODO: save the mac address to db along with user and date
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
RefreshTokens.TryAdd(guid, refreshTokenTicket);
context.SetToken(guid);
return Task.FromResult<object>(null);
}
public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (RefreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
return Task.FromResult<object>(null);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
}
Now if i understand correctly. The purpose of the SimpleRefreshTokenProvider is to build up a RefreshToken and to the validate it when the api receives a request with one in it?
Step 3: SimpleAuthorizationServerProvider. This is what I have so far. but I have a feeling this is where I have gone wrong. Or im getting confused, What is the purpose of this class? Is it not to validate the AccessToken?
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Not concerned about clients yet
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// validate user credentials
var userManager = context.OwinContext.GetUserManager<FskUserManager>();
FskUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// create identity
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
//Set properties of the token
// create metadata to pass on to refresh token provider
AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>
{
{"userName", user.UserName}
});
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
string originalClient;
context.Ticket.Properties.Dictionary.TryGetValue("as:client_id", out originalClient);
var currentClient = context.ClientId;
// chance to change authentication ticket for refresh token requests
var newId = new ClaimsIdentity(context.Ticket.Identity);
newId.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
context.Validated(newTicket);
}
}
Please what am I missing here?
How to use claims? For example, I want to set access to each page (resource) for each user. I understand, I can do it using roles, but as I understand, claim-based is more effectively. But when I try to create a claim, I see the following method:
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "test role"));
first parameter of constructor of Claim class get ClaimTypes enum, which has many "strange" members like Email, Phone etc. I want to set that this claim and then check this claim to have access to certain resource. I'm on wrong way? How to do it?
From the code above, I am assuming you have already added the claim in startup class on authenticated of your provider as below.
context.Identity.AddClaim(new Claim("urn:google:name", context.Identity.FindFirstValue(ClaimTypes.Name))); // added claim for reading google name
context.Identity.AddClaim(new Claim("urn:google:email", context.Identity.FindFirstValue(ClaimTypes.Email))); // and email too
Once you have added the claims in startup, when the request is actually processed check if its a callback and if yes, read the claims as below(in IHttpHandler).
public void ProcessRequest(HttpContext context)
{
IAuthenticationManager authManager = context.GetOwinContext().Authentication;
if (string.IsNullOrEmpty(context.Request.QueryString[CallBackKey]))
{
string providerName = context.Request.QueryString["provider"] ?? "Google";//I have multiple providers so checking if its google
RedirectToProvider(context, authManager, providerName);
}
else
{
ExternalLoginCallback(context, authManager);
}
}
If its 1st call redirect to provider
private static void RedirectToProvider(HttpContext context, IAuthenticationManager authManager, string providerName)
{
var loginProviders = authManager.GetExternalAuthenticationTypes();
var LoginProvider = loginProviders.Single(x => x.Caption == providerName);
var properties = new AuthenticationProperties()
{
RedirectUri = String.Format("{0}&{1}=true", context.Request.Url, CallBackKey)
};
//string[] authTypes = { LoginProvider.AuthenticationType, DefaultAuthenticationTypes.ExternalCookie };
authManager.Challenge(properties, LoginProvider.AuthenticationType);
//without this it redirect to forms login page
context.Response.SuppressFormsAuthenticationRedirect = true;
}
And finally read the claims you get back
public void ExternalLoginCallback(HttpContext context, IAuthenticationManager authManager)
{
var loginInfo = authManager.GetExternalLoginInfo();
if (loginInfo == null)
{
throw new System.Security.SecurityException("Failed to login");
}
var LoginProvider = loginInfo.Login.LoginProvider;
var ExternalLoginConfirmation = loginInfo.DefaultUserName;
var externalIdentity = authManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
var pictureClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("picture"));
var pictureUrl = pictureClaim.Value;
LogInByEmail(context, email, LoginProvider); //redirects to my method of adding claimed user as logged in, you will use yours.
}
Claim doesn't set permission. It's used to verify you that "you are who you claim to be you are". These claims are identified by issuer, usually a 3rd party. See for example this article for description.
So, you should define which claims are necessary (who user should be) in order to access a certain page. Otherwise, using claim-based authorization will be same as using identity based or role based.