I am developing an intranet Web App using windows auth. I am using Claims Transformation using custom ClaimsAuthenticationManager.
public class ClaimsTransformer : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
var newPrinciPal = CreateApplicationPrincipal(incomingPrincipal.Identity.Name);
EstablishSession(newPrinciPal);
return newPrinciPal;
}
private void EstablishSession(ClaimsPrincipal newPrinciPal)
{
var sessionToken = new SessionSecurityToken(newPrinciPal, TimeSpan.FromHours(8))
{
IsPersistent = false,
IsReferenceMode = true
};
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
}
private ClaimsPrincipal CreateApplicationPrincipal(string userName)
{
new SecurityManager().LoginUser(userName);
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, userName));
RPSUser originalIdentity = Thread.CurrentPrincipal.Identity as RPSUser;
if (originalIdentity != null)
{
RPSUserInformation userDetails = originalIdentity.UserDetails;
int splitIndex = userDetails.FullName.IndexOf(' ');
claims.Add(new Claim(ClaimTypes.GivenName, userDetails.FullName.Substring(0, splitIndex)));
claims.Add(new Claim(ClaimTypes.Surname, userDetails.FullName.Substring(splitIndex + 1)));
claims.Add(new Claim(ClaimTypes.Email, userDetails.Email));
claims.Add(new Claim(ClaimTypes.WindowsAccountName, userDetails.LanId));
}
RPSUser transformedIdentity = new RPSUser(new ClaimsIdentity(claims, "Custom"));
transformedIdentity.UserDetails = originalIdentity.UserDetails;
return new RPSPrincipal(transformedIdentity, null);
}
}
In Global.ascx I have this code
protected void Application_PostAuthenticateRequest()
{
try
{
var newPrincipal = FederatedAuthentication.FederationConfiguration
.IdentityConfiguration
.ClaimsAuthenticationManager.Authenticate(string.Empty, ClaimsPrincipal.Current);
SecurityManager.SetIdentityToThread(newPrincipal as RPSPrincipal);
}
catch
{
}
}
Also I am able to retrieve/deserialize the Cookie from this piece of code in Global.asax
void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e)
{
var extendSession = true;
if (Request.UrlReferrer.AbsolutePath.Equals("/"))
{
var principal = e.SessionToken.ClaimsPrincipal as RPSPrincipal;
SecurityManager.SetIdentityToThread(principal);
if (extendSession)
{
e.SessionToken = new SessionSecurityToken(
e.SessionToken.ClaimsPrincipal,
TimeSpan.FromHours(8))
{
IsPersistent = false,
IsReferenceMode = true
};
e.ReissueCookie = extendSession;
}
}
}
But the problem is even though I am able to deserialize the cookie to the custom Principal (RPSPrincipal) in this event and able to set it to thread, subsequently in the Application_PostAuthenticateRequest (for the same request) we are losing the Custom Principal. We are just getting ClaimsPrincipal there.
What am I doing wrong here? How could I ensure that windows authentication does not overwrite principal when there is a custom principal is already set and IsAuthenticated property of Identity is already set to true?
Or is there anyway to access the sessionSecurityToken at Application_PostAuthenticateRequest event?
Related
I want to sign my users in using their email address from Facebook. I have configured my Facebook authentication:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions
{
AppId = facebookId,
AppSecret = facebookSecret,
Provider = new FacebookProvider()
};
app.UseFacebookAuthentication(facebookAuthenticationOptions);
I have overridden the Facebook provider to also return the email address:
public class FacebookProvider : FacebookAuthenticationProvider
{
public override Task Authenticated(FacebookAuthenticatedContext context)
{
var accessTokenClaim = new Claim("ExternalAccessToken", context.AccessToken, "urn:facebook:access_token");
context.Identity.AddClaim(accessTokenClaim);
var extraClaims = GetAdditionalFacebookClaims(accessTokenClaim);
context.Identity.AddClaim(new Claim(ClaimTypes.Email, extraClaims.First(k => k.Key == "email").Value.ToString()));
context.Identity.AddClaim(new Claim("Provider", context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
var userDetail = context.User;
var link = userDetail.Value<string>("link") ?? string.Empty;
context.Identity.AddClaim(new Claim("link", link));
context.Identity.AddClaim(new Claim("FacebookId", userDetail.Value<string>("id")));
return Task.FromResult(0);
}
private static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new { fields = new[] { "email" } }) as JsonObject;
}
Everything works fine in MVC - in the LoginOwinCallback function, I am able to retrieve the user's email address as returned from Facebook. I am trying to achieve the same thing in WebApi using token authentication instead of external cookies. However, although I can see my provider adding the email claim to the response, when I call the AuthenticateAsync method in the following routine, the Email claim is not included.
private async Task<ExternalLoginInfo> GetExternalLoginInfoAsync()
{
var result = await Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
if (result == null || result.Identity == null) return null;
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim != null)
{
return new ExternalLoginInfo()
{
DefaultUserName = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", ""),
ExternalIdentity = result.Identity,
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value)
};
}
return null;
}
Any ideas what I am doing wrong?
For anyone else facing the same problem, the Email claim is available - just a bit hidden.
Firstly, to correctly retrieve the user's email address from Facebook, the authentication set-up in the ConfigureAuth method of Startup.Auth.cs should be:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = facebookId,
AppSecret = facebookSecret,
Scope= {"email"},
UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=email"
});
In the LoginOwinCallback method of the MVC AccountController class, the email address is found in the Email property of the ExternalLoginInfo object returned by var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();.
To retrieve the email address in the WebAPI AccountController class, you will need to cast the User.Identity object to the ClaimsIdentity type.
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
// get all of the claims
var claimsIdentity = User.Identity as ClaimsIdentity; // this cast exposes all of the claims returned by Facebook
// get the external login details
var externalLogin = ExternalLoginData.FromIdentity(claimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
var user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
var hasRegistered = user != null;
if (hasRegistered)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
var cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
var properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
AuthenticationManager.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
var claims = claimsIdentity?.Claims ?? externalLogin.GetClaims();
var identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
AuthenticationManager.SignIn(identity);
}
return Ok();
}
This means you can then use the FindFirst method on the Identity to find the email claim returned by Facebook.
In MVC5 ASP.Identity replaces old form authentication. However as per the discussion here A type of FormsAuthentication still exists though. According to Microsoft,
But i also found Microsoft.Owin.Security.Forms library is also deprecated (check this nuget link)
What are my options here if i want to use ASP.NET MVC5 and i want to store userid & password in SQL table ( eg aspnet_users & aspnet_membership SQL tables)
( this should be a quick temporary solution until we move to new OpenIdConnect)
ASP.NET Identity does support cookie based authentication out of the box, allowing you to store logins in DB and having a "forms authentication like" mechanism. Default tables schema are not the same than with membership, but it is customizable.
Bootstrapping sample:
[assembly: OwinStartup(typeof(YourNamespace.Startup))]
namespace YourNamespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var options = GetCookieOptions();
app.UseCookieAuthentication(options);
}
public static CookieAuthenticationOptions GetCookieOptions()
{
var options = new CookieAuthenticationOptions
{
AuthenticationType =
DefaultAuthenticationTypes.ApplicationCookie,
SlidingExpiration = true,
// On ajax calls, better have a 401 rather than a redirect
// to an HTML login page.
// Taken from http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
// Patching by the way the absolute uri using http
// instead of https, when we are behind a lb
// terminating the https: returning only
// PathAndQuery
ctx.Response.Redirect(new Uri(ctx.RedirectUri)
.PathAndQuery);
}
}
}
};
if (!string.IsNullOrEmpty(Settings.Default.LoginPath))
options.LoginPath = new PathString(Settings.Default.LoginPath);
if (!string.IsNullOrEmpty(Settings.Default.AuthCookieName))
options.CookieName = Settings.Default.AuthCookieName;
if (!string.IsNullOrEmpty(Settings.Default.AuthCookieDomain))
options.CookieDomain = Settings.Default.AuthCookieDomain;
if (Settings.Default.ForceSecuredCookie)
options.CookieSecure = CookieSecureOption.Always;
return options;
}
// Taken from http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
private static bool IsAjaxRequest(IOwinRequest request)
{
var query = request.Query;
if (query != null && StringComparer.OrdinalIgnoreCase.Equals(
query["X-Requested-With"], "XMLHttpRequest"))
return true;
var headers = request.Headers;
return headers != null && StringComparer.OrdinalIgnoreCase.Equals(
headers["X-Requested-With"], "XMLHttpRequest");
}
}
}
(Settings.Default. are custom configuration properties of the project in those sample.)
sign-in, sign-out sample:
UserManager<IdentityUser> yourUserManager;
public bool SignIn(string login, string password, bool rememberMe)
{
var user = yourUserManager.Find(userName, password);
if (user == null)
return false;
var expiration = rememberMe ?
Settings.Default.PermanentAuthCookieExpiration :
Settings.Default.AuthCookieExpiration;
var authenticationManager =
HttpContext.Current.GetOwinContext().Authentication;
var claimsIdentity = yourUserManager.CreateIdentity(user,
DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(
new AuthenticationProperties
{
AllowRefresh = true,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(expiration),
IsPersistent = rememberMe
}, claimsIdentity);
return true;
}
public void IIdentityUserManager.SignOut()
{
var authenticationManager =
HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut();
}
And of course, with MVC, use AuthorizeAttribute as a global filter along with [AllowAnonymous] on actions which do not require authorization.
I am using Thinktecture AuthenticationConfiguration to provide an end point for signing tokens on my API:
var authConfig = new AuthenticationConfiguration
{
EnableSessionToken = true,
SendWwwAuthenticateResponseHeaders = true,
RequireSsl = false,
ClaimsAuthenticationManager = new ClaimsTransformation(),
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/api/token",
SigningKey = signingKey,
DefaultTokenLifetime = new TimeSpan(1, 0, 0)
}
};
var userCredentialsService = new CredentialsService(credentialStore);
authConfig.AddBasicAuthentication(userCredentialsService.Validate);
And authenticating users with CredentialsService:
public class CredentialsService
{
public bool Validate(string username, string password)
{
return username == password;
}
}
The above works, and no its certainly not used in production, but on returning true i will get a token in which contains a claim with the username.
In my scenario I have a user id (an integer) which can never change and I would like this to be in my claim. So the user would pass an email address to the service endpoint in the header as basic authentication, and then if valid go ahead and sign with the id as the claim (but not the email address as the claim):
public class CredentialsService
{
public bool Validate(string emailAddress, string password)
{
// map from the provided name, to the user id
var details = MySqlDb.ReadBy(emailAddress);
var id = details.Id; // this is the actual identity of the user
var email = details.EmailAddress;
var hash = details.Hash;
return PasswordHash.ValidatePassword(password,hash);
}
}
I appreciate this will need a second lookup to a sql server database to transform the emailAddress in to a userId, is there a way for me to insert this in to the pipeline flow before CredentialsService is called?
Or am i going about it the wrong way, and just stick with the username that was signed in as, then use a claims transformation based on the username to enrich with the integer identity - but then what if they changed the username?
Ok, I managed to solve this by taking a look at the awesome thinktecture source and overriding BasicAuthenticationSecurityTokenHandler to give a derived class which has a second delegate returning a Claim[] ready to be signed:
public class BasicAuthSecurityTokenHandlerWithClaimsOutput : BasicAuthenticationSecurityTokenHandler
{
public BasicAuthSecurityTokenHandlerWithClaimsOutput(ValidateUserNameCredentialDelegate validateUserNameCredential, GetClaimsForAuthenticatedUser getClaimsForAuthenticatedUser)
: base()
{
if (validateUserNameCredential == null)
{
throw new ArgumentNullException("ValidateUserNameCredential");
}
if (getClaimsForAuthenticatedUser== null)
{
throw new ArgumentNullException("GetClaimsForAuthenticatedUser");
}
base.ValidateUserNameCredential = validateUserNameCredential;
_getClaimsForAuthenticatedUser = getClaimsForAuthenticatedUser;
}
public delegate Claim[] GetClaimsForAuthenticatedUser(string username);
private readonly GetClaimsForAuthenticatedUser _getClaimsForAuthenticatedUser;
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
{
if (token == null)
{
throw new ArgumentNullException("token");
}
if (base.Configuration == null)
{
throw new InvalidOperationException("No Configuration set");
}
UserNameSecurityToken unToken = token as UserNameSecurityToken;
if (unToken == null)
{
throw new ArgumentException("SecurityToken is not a UserNameSecurityToken");
}
if (!ValidateUserNameCredentialCore(unToken.UserName, unToken.Password))
{
throw new SecurityTokenValidationException(unToken.UserName);
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, unToken.UserName),
new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password),
AuthenticationInstantClaim.Now
};
var lookedUpClaims = _getClaimsForAuthenticatedUser(unToken.UserName);
claims.AddRange(lookedUpClaims);
if (RetainPassword)
{
claims.Add(new Claim("password", unToken.Password));
}
var identity = new ClaimsIdentity(claims, "Basic");
if (Configuration.SaveBootstrapContext)
{
if (this.RetainPassword)
{
identity.BootstrapContext = new BootstrapContext(unToken, this);
}
else
{
var bootstrapToken = new UserNameSecurityToken(unToken.UserName, null);
identity.BootstrapContext = new BootstrapContext(bootstrapToken, this);
}
}
return new List<ClaimsIdentity> {identity}.AsReadOnly();
}
}
I then added a second helper method to make it easier to wire up:
public static class BasicAuthHandlerExtensionWithClaimsOutput
{
public static void AddBasicAuthenticationWithClaimsOutput(
this AuthenticationConfiguration configuration,
BasicAuthenticationSecurityTokenHandler.ValidateUserNameCredentialDelegate validationDelegate,
BasicAuthSecurityTokenHandlerWithClaimsOutput.GetClaimsForAuthenticatedUser getClaimsForAuthenticatedUserDelegate,
string realm = "localhost", bool retainPassword = false)
{
var handler = new BasicAuthSecurityTokenHandlerWithClaimsOutput(validationDelegate, getClaimsForAuthenticatedUserDelegate);
handler.RetainPassword = retainPassword;
configuration.AddMapping(new AuthenticationOptionMapping
{
TokenHandler = new SecurityTokenHandlerCollection { handler },
Options = AuthenticationOptions.ForAuthorizationHeader(scheme: "Basic"),
Scheme = AuthenticationScheme.SchemeAndRealm("Basic", realm)
});
}
}
Hope this helps others, please let me know if i have done something horrific!
I have my web application deployed on Microsoft Azure. However when I want to generate a PasswordResetToken with:
var token = await _userManager.GeneratePasswordResetTokenAsync(user.Id);
I get the following error:
System.Security.Cryptography.CryptographicException: The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread's user context, which may be the case when the thread is impersonating.
How do I get this to work on Azure?
Or is there an other way to reset a password without knowing the old password?
This is my UserManager class. Mabey there is an error in it.
public class ApplicationUserManager : UserManager<ApplicationIdentityUser>
{
private static IUnitOfWork _unitOfWork;
private readonly IRepository<ApplicationIdentityUser> _userRepository;
public ApplicationUserManager(IUserStore<ApplicationIdentityUser> store, IRepository<ApplicationIdentityUser> userRepository)
: base(store)
{
if (userRepository == null) throw new ArgumentNullException("userRepository");
_userRepository = userRepository;
if (bool.Parse(ConfigurationManager.AppSettings["RunningInAzure"]))
UserTokenProvider = new EmailTokenProvider<ApplicationIdentityUser, string>();
else
{
var provider = new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider("TopRijden");
UserTokenProvider = new DataProtectorTokenProvider<ApplicationIdentityUser, string>(provider.Create("Password Reset"));
}
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
if (options == null) throw new ArgumentNullException("options");
if (context == null) throw new ArgumentNullException("context");
try
{
_unitOfWork = ObjectFactory.GetInstance<IUnitOfWork>();
var userRepository = ObjectFactory.GetInstance<IRepository<ApplicationIdentityUser>>();
var manager = new ApplicationUserManager(new UserStore<ApplicationIdentityUser>(_unitOfWork.Session), userRepository);
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationIdentityUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug in here.
manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationIdentityUser>
{
MessageFormat = "Your security code is: {0}"
});
manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationIdentityUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is: {0}"
});
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationIdentityUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
catch (Exception ex)
{
ex.Process(MethodBase.GetCurrentMethod().DeclaringType, MethodBase.GetCurrentMethod().Name);
return null;
}
}
}
}
I found a working solution for my own problem based on the answer of trailmax.
In stead of the EmailTokenProvider I use the TotpSecurityStampBasedTokenProvider
public UserManager() : base(new UserStore<ApplicationUser>(new MyDbContext()))
{
// other setup
this.UserTokenProvider = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();
}
For more information about TotpSecurityStampBasedTokenProvider:
http://msdn.microsoft.com/en-us/library/dn613297(v=vs.108).aspx
Use EmailTokenProvider in UserManager
public UserManager() : base(new UserStore<ApplicationUser>(new MyDbContext()))
{
// other setup
this.UserTokenProvider = new EmailTokenProvider<ApplicationUser, string>();
}
I've blogged about it recently.
I am currently using windows azure active directory as a single sign-on in my MVC.NET application and that portion works great. I can authenticate against WAAD and get my ClaimsPrinicipal loaded without any problems.
The next step was to transform the claims retrieved from WAAD by adding new claims from a different data source. To this extent I created a class inheriting the ClaimsAuthenticationManager (below). The claims get added to the Principal and get persisted to the session cookie in the CreateSession method.
My problem right now is that ClaimsPrincipal.Current does not carry any of the additional claims that I've added. When I set a breakpoint in the SessionAuthenticationModule_SessionSecurityTokenReceived event, I can see that there's a discrepancy between the ClaimsPrincipal.Current
ClaimsPrincipal.Current.FindAll(ClaimTypes.Email)
Count = 0
and e.SessionToken.ClaimsPrincipal.
e.SessionToken.ClaimsPrincipal.FindAll(ClaimTypes.Email)
Count = 1
[0]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: me#mydomain.com}
What am I missing here? In all the samples that dealt with transforming claims that I could find there's no mention of manually reloading the ClaimsPrinicipal from the cookie. Would the session security token event be the right place to reload the ClaimsPrincipal or am I breaking the security model?
Thanks.
public class MyAuthenticationManager : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
var transformedPrincipal = this.CreateUserPrincipal(incomingPrincipal.Identity.Name);
this.CreateSession(transformedPrincipal);
return transformedPrincipal;
}
private ClaimsPrincipal CreateUserPrincipal(String userName)
{
List<Claim> claims = new List<Claim>();
var user = SecurityController.GetUserIdentity(userName);
claims.Add(new Claim(ClaimTypes.Name, userName));
claims.Add(new Claim(ClaimTypes.Email, user.Email));
claims.Add(new Claim(ClaimTypes.GivenName, user.FirstName));
claims.Add(new Claim(ClaimTypes.Surname, user.LastName));
return new ClaimsPrincipal(new ClaimsIdentity(claims, "MyCustom"));
}
private void CreateSession(ClaimsPrincipal transformedPrincipal)
{
var sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
if (FederatedAuthentication.SessionAuthenticationModule != null &&
FederatedAuthentication.SessionAuthenticationModule.ContainsSessionTokenCookie(HttpContext.Current.Request.Cookies))
{
return;
}
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
//Added line below as per suggestion in one of the posts
//Doesn't seem to have any effect
Thread.CurrentPrincipal = transformedPrincipal;
FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenReceived += SessionAuthenticationModule_SessionSecurityTokenReceived;
}
void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("SessionAuthenticationModule_SessionSecurityTokenReceived");
}
Looks like I had to access the claims through ClaimsIdentity instead of ClaimsPrincipal. Now I can successfully access the claims from any view or controller in my application.
((ClaimsIdentity)Thread.CurrentPrincipal.Identity).FindAll(ClaimTypes.Email)
Count = 1
[0]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: me#mydomain.com}
Final codebase in AuthenticationManager looks like this (note that there is no explicit assignment operation to the ClaimsPrincipal on the current thread).
public class MyAuthenticationManager : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
var transformedPrincipal = this.CreateUserPrincipal(incomingPrincipal.Identity.Name);
this.CreateSession(transformedPrincipal);
return transformedPrincipal;
}
private ClaimsPrincipal CreateUserPrincipal(String userName)
{
List<Claim> claims = new List<Claim>();
var user = SecurityController.GetUserIdentity(userName);
claims.Add(new Claim(ClaimTypes.Name, userName));
claims.Add(new Claim("UserId", user.Id.ToString()));
claims.Add(new Claim(ClaimTypes.Email, user.Email));
claims.Add(new Claim(ClaimTypes.GivenName, user.FirstName));
claims.Add(new Claim(ClaimTypes.Surname, user.LastName));
//claims.Add(new Claim(ClaimTypes.NameIdentifier, userName));
if (user.Account != null)
{
claims.Add(new Claim("AccountId", user.Account.Id.ToString()));
claims.Add(new Claim("AccountName", user.Account.Name.ToString()));
}
if (user.Owner != null)
{
claims.Add(new Claim("OwnerId", user.Owner.Id.ToString()));
claims.Add(new Claim("OwnerName", user.Owner.Name.ToString()));
}
return new ClaimsPrincipal(new ClaimsIdentity(claims, "MyCustom"));
}
private void CreateSession(ClaimsPrincipal transformedPrincipal)
{
if (FederatedAuthentication.SessionAuthenticationModule != null &&
FederatedAuthentication.SessionAuthenticationModule.ContainsSessionTokenCookie(HttpContext.Current.Request.Cookies))
{
return;
}
var sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
}
}
I don't see you adding your ClaimsPrincipal (after the transformation) back to Thread.CurrentPrincipal. Please try
private void CreateSession(ClaimsPrincipal transformedPrincipal)
{
var sessionSecurityToken = new SessionSecurityToken(transformedPrincipal, TimeSpan.FromHours(8));
if (FederatedAuthentication.SessionAuthenticationModule != null &&
FederatedAuthentication.SessionAuthenticationModule.ContainsSessionTokenCookie(HttpContext.Current.Request.Cookies))
{
return;
}
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);
//Following is the missing line of code.
Thread.CurrentPrincipal = transformedPrincipal;
}