I am adding custom claim to the User.Identity for a web site using MVC5 and OWIN authentication. But I'm using local account sign in.
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalCookie);
identity.AddClaim(new Claim("TenantID", user.TenantID.ToString()));
AuthenticationManager.SignIn(new AuthenticationProperties()
{
IsPersistent = isPersistent
}, identity);
return await SignInOrTwoFactor(user, isPersistent);
But when I try to retrieve back, my custom claim does not exist in the collection. This is from IdentityExtension class :
public static short TenantID(this IIdentity identity)
{
if (identity == null) throw new ArgumentNullException("identity");
var ci = identity as ClaimsIdentity;
var value = ci != null ? ci.FindFirstValue(GlobalVariables.TenantIdIdentifier) : "0";
return short.Parse(value);
}
This is my startup code:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
}); }
It works this way :
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalCookie);
await UserManager.AddClaimAsync(user.Id, new Claim(GlobalVariables.TenantIdIdentifier, user.TenantID.ToString()));
AuthenticationManager.SignIn(new AuthenticationProperties()
{
IsPersistent = isPersistent
}, identity);
return await SignInOrTwoFactor(user, isPersistent);
Related
I want to set an expiry time for my web app so that after 1 hour the user will automatically be logged out. It seems that azure now have a new feature called 'conditional access'. Firstly I have to have a premium account (so yet more money) and secondly I cant find anything in the documentation that shows how to use it to log someone out after a specified time. Has anyone used this feature yet ? how can this be done ?
can anyone help ?
Assuming that you are using OpenID Connect and Cookie authentication middleware to protect your web app, for your requirement, I assumed that you could add a custom claim named loggedTicks and check the time interval under the OnValidateIdentity of CookieAuthenticationProvider, then explicitly invoke the sign out operation against your web application and AAD. Here is the code snippet, you could refer to it:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
ExpireTimeSpan=TimeSpan.MaxValue,
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = ctx => {
var loggedClaim=ctx.Identity.FindFirst("loggedTicks")?.Value;
if (loggedClaim != null)
{
var loggedDateTime = new DateTime(long.Parse(loggedClaim), DateTimeKind.Utc);
if (loggedDateTime.AddHours(1) < DateTime.UtcNow)
{
ctx.RejectIdentity();
ctx.OwinContext.Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
}
return Task.FromResult(0);
}
}
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenValidated = async (x) =>
{
var identity = x.AuthenticationTicket.Identity;
//add a additional claim which represents the current user logged UTC time ticks
identity.AddClaim(new System.Security.Claims.Claim("loggedTicks", DateTime.UtcNow.Ticks.ToString()));
await Task.FromResult(0);
}
}
});
}
I have a set of claims. I'm using it to create a ClaimsIdentity. I also use OWIN to signin the identity. In addition, I'm adding it to the ClaimsPrincipal.Current.Identities. Here is my code...
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> LogonCallBack()
{
var token = Request.Params["id_token"];
var validatedToken = TokenService.ValidateIdToken(token);
var identity = new ClaimsIdentity(validatedToken.Claims);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, identity);
ClaimsPrincipal.Current.AddIdentity(identity);
return RedirectToAction("Display");
//return RedirectToAction("Index", "Error", new { area = "Token Validate Failed." });
}
When debugging, I see that the set of claims that i am retrieving are coming across fine. And I can create the ClaimsIdentity. However, after this, when I am redirected to the Display page, the User.Identity.IsAuthenticated is still false. ClaimsPrincipal.Current does not have the added identity in its list.
How am I able to get the user to be authenticated?
You should use SecurityHelper to Add an Identity to Current User.
var owinContext = HttpContext.GetOwinContext();
var securityHelper = new Microsoft.Owin.Security.Infrastructure.SecurityHelper(owinContext);
securityHelper.AddUserIdentity(identity);
I added the cookietype string to the identity declaration:
var identity = new ClaimsIdentity(validatedToken.Claims, DefaultAuthenticationTypes.ApplicationCookie);
And I also added the same string to the middleware pipeline as follows:
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/OIDC/Authenticate")
});
I am using Azure AD to authenticate the users. I want to add few user claims specific to my application. Should I do it in Application_PostAuthenticateRequest` in global.asax ?. Is there a way I can cache my claims too ?
If you are using the ASP.NET OWIN middleware, there are specific notifications you can use for that purpose. Claims added in that way will end up in your session cookie, so that you won't have to repeat the claims augmentation logic in subsequent calls. See http://www.cloudidentity.com/blog/2015/08/26/augmenting-the-set-of-incoming-claims-with-the-openid-connect-and-oauth2-middleware-in-katana-3-x/ for details.
BTW you can add your custom cliams but you cannot override the existing claims added by the Azure AD (what i have seen so far might be i am wrong). what you can do is to add the new cliams like this
AuthorizationCodeReceived = context =>
{
List<System.Security.Claims.Claim> allcustomClaims = new List<System.Security.Claims.Claim>();
allcustomClaims.Add(new System.Security.Claims.Claim("customClaim", "YourDefindedValue"));
context.AuthenticationTicket.Identity.AddClaims(allcustomClaims);
return Task.FromResult(0);
}`
and then you can get the claim anywhere in controller like
#{
var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
if (claimsIdentity != null)
{
var c = claimsIdentity.FindFirst("customClaim").Value;
}
}
You can augment the claims programmatically like this:
public async Task<ActionResult> AuthenticateAsync()
{
ClaimsPrincipal incomingPrincipal = System.Threading.Thread.CurrentPrincipal as ClaimsPrincipal;
if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true)
{
ClaimsIdentity claimsIdentity = incomingPrincipal.Identity as ClaimsIdentity;
if (!claimsIdentity.HasClaim(ClaimTypes.Role, "Admin"))
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "AADGuide"));
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
AuthenticateResult authResult = await authenticationManager.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType);
authenticationManager.SignIn(authResult.Properties,claimsIdentity);
}
}
return RedirectToAction("Index", "Start");
}
This solution relies on AuthenticationAsync method of AuthenticationManager to retrieve the original AuthenticationProperties. After retrieving the properties, call the SignIn method to persist the new ClaimsIdentity in the auth cookie.
If you're making use of:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
...
This is how I managed to add additional custom claims using new OAuthBearerAuthenticationProvider:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
// The id of the client application that must be registered in Azure AD.
TokenValidationParameters = new TokenValidationParameters { ValidAudience = clientId },
// Our Azure AD tenant (e.g.: contoso.onmicrosoft.com).
Tenant = tenant,
Provider = new OAuthBearerAuthenticationProvider
{
// In this handler we can perform additional coding tasks...
OnValidateIdentity = async context =>
{
try
{
// Retrieve user JWT token from request.
var authorizationHeader = context.Request.Headers["Authorization"].First();
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
// Get current user identity from authentication ticket.
var authenticationTicket = context.Ticket;
var identity = authenticationTicket.Identity;
// Credential representing the current user. We need this to request a token
// that allows our application access to the Azure Graph API.
var userUpnClaim = identity.FindFirst(ClaimTypes.Upn);
var userName = userUpnClaim == null
? identity.FindFirst(ClaimTypes.Email).Value
: userUpnClaim.Value;
var userAssertion = new UserAssertion(
userJwtToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
identity.AddClaim(new Claim(identity.RoleClaimType, "myRole"));
}
catch (Exception e)
{
throw;
}
}
}
});
For a full sample, check this blog post.
I am replacing the (HttpContext.Current.User) IPrincipal with a custom version so I can store more information login and the user. I have done this before using the FormsAuthtenticationTicket, but those other ways were based on the Memberhipship and SimpleMembership providers.
My question is, can i use the FormsAuthenticationTicket to store the cookie of my ICustomPrincipal with it interfering or breaking in OWIN Identity Pipline? I feel like would i be mixing apples and oranges.
example save:
var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.UserId = user.Id;
serializeModel.FirstName = user.FirstName;
serializeModel.LastName = user.LastName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
viewModel.Email,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
example retrieve:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.UserId = serializeModel.UserId;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
HttpContext.Current.User = newUser;
}
}
EDIT
I have this for the creating the claim
public ClaimsIdentity CreateIdentity(
LoginAttempt loginAttempt)
{
UserProfile userProfile = GetUserProfile(loginAttempt.UserName);
var applicationUser = FindById(userProfile.AspNetUserId);
ClaimsIdentity identity;
try
{
identity = UserManager.CreateIdentity(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
}
catch (Exception ex)
{
_log.Error(ex.Message, ex);
return null;
}
//UserManager.GetClaims()
identity.AddClaim(new Claim("LoginAttemptId", loginAttempt.LoginAttemptId.ToString(),ClaimValueTypes.String));
identity.AddClaim(new Claim("UserProfileId", loginAttempt.UserProfileId.ToString(), ClaimValueTypes.String));
identity.AddClaim(new Claim("SubscriptionType", userProfile.SubscriptionType, ClaimValueTypes.String));
IList<string> roles= UserManager.GetRoles(applicationUser.Id);
identity.AddClaim(new Claim(ClaimTypes.Role, roles.First()));
return identity;
}
and this for extracting
public static long GetLoginAttemptId(this IIdentity principal)
{
var claimsPrincipal = principal as ClaimsIdentity;
if (claimsPrincipal == null)
{
//throw new Exception("User is not logged in!");
return -1;
}
var nameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "LoginAttemptId");
if (nameClaim != null)
{
return Convert.ToInt64( nameClaim.Value);// as long;
}
return -1;
}
EDIT
These are the claims I am getting. I have logged off and logged back in.
There are Claims that serve exactly the same purpose. Only new API is actually purposed this way.
Claims are a basically a Dictionary<String, String> that is stored in auth-cookie and available through IPrincipal. But you don't need to do ICustomPrincipal because actual object that you get behind IPrincipal is ClaimsPrincipal and that has a list of claims.
You'd add extra information to Idnentity object just before the login:
public async override Task CreateIdentityAsync(ApplicationUser applicationUser)
{
var identity = await base.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim("MyApp:FullName", applicationUser.FullName));
return identity;
}
And then you'd be able to get this data out from IPrincipal via extension:
public static String GetFullName(this IPrincipal principal)
{
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
{
throw new Exception("User is not logged in!");
}
var nameClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:FullName");
if (nameClaim != null)
{
return nameClaim.Value;
}
return String.Empty;
}
I have used this method successfully in a few projects already. See other similar answers for more code samples.
Here is another article, though I discourage from using Thread.CurrentPrincipal or ClaimsPrincipal.Current in MVC application - you don't always get what you expect, especially when user is not logged in or on early stages of AppPool start up.
This approach works for me (Using MVC4), slightly different from above.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("MyApp:OrganizationId", OrganizationID.ToString()));
return userIdentity;
}
public int OrganizationID { get; set; }
}
Extension method. Note you should use claimsPrincipal variable (instead of principal variable) to obtain the claims. I think that's a little mistake in the excelent answer of #trailmax (sorry for not comment this in your answer, my reputation doesn't allow me). Also, I use IIdentity instead of IPrincipal
public static class IdentityExtensions
{
public static int GetOrganizationId(this IIdentity principal)
{
var claimsPrincipal = principal as ClaimsIdentity;
if (claimsPrincipal == null)
{
throw new Exception("User is not logged in!");
}
var nameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:OrganizationId");
if (nameClaim != null)
{
return int.Parse(nameClaim.Value);
}
throw new Exception("ID doesn't exists");
}
}
And then, I use the extension method in the controller like this:
var organizationId = User.Identity.GetOrganizationId();
Hope this is useful to someone.
Suppose that I have a web site that accept user by randomly. Rule is simple; random generator can tell user can sign in or not.
So how should I use this code?
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
I don't have ApplicationUser, I don't have injected DbContext, I don't have users (!) table.
Main question; Where is my simple authentication?
FormsAuthentication.RedirectFromLoginPage ("I_am_a_User", Persist.Checked)
code look like ugly but I solved...
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
List<Claim> _claims = new List<Claim>()
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
Guid.NewGuid().ToString(),
"http://www.w3.org/2001/XMLSchema#string", "LOCAL AUTHORITY"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"I_am_a_User",
"http://www.w3.org/2001/XMLSchema#string", "LOCAL AUTHORITY"),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
"ASP.NET Identity",
"http://www.w3.org/2001/XMLSchema#string", "LOCAL AUTHORITY"),
};
ClaimsIdentity identity = new ClaimsIdentity(_claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);