private async Task<AuthenticateResult> GetAppropriateAuthenticationResult(ExternalIdentity externalIdentity) { var account = await _userRepository.GetUserForExternalProviderAsync(externalIdentity.Provider, externalIdentity.ProviderId); if (account != null) { return CreateSuccesFullAuthentication(externalIdentity, account); } var emailClaim = externalIdentity.Claims.FirstOrDefault(c => c.Type == "email"); if (emailClaim == null) { return new AuthenticateResult("An email is claim required to authenticate."); } var userWithMatchingEmailClaim = await _userRepository.GetUserByEmailAsync(emailClaim.Value); if (userWithMatchingEmailClaim == null && externalIdentity.Provider == "windows") { return AskWindowsAuthenticatedUserForAdditionalInfo(externalIdentity); } if (userWithMatchingEmailClaim == null) { return CreateNewUserAndAuthenticate(externalIdentity); } return await AuthenticateExistingUserWithNewExternalProvider( externalIdentity, userWithMatchingEmailClaim); }
private AuthenticateResult CreateNewUserAndAuthenticate(ExternalIdentity externalIdentity) { var newUser = ConfigureNewUser(externalIdentity); _userRepository.SaveUser(newUser); return CreateSuccesFullAuthentication(externalIdentity, newUser); }
private async Task<AuthenticateResult> AuthenticateExistingUserWithNewExternalProvider( ExternalIdentity externalIdentity, User userWithMatchingEmailClaim) { await _userRepository.AddUserLoginAsync( userWithMatchingEmailClaim.Subject, externalIdentity.Provider, externalIdentity.ProviderId); return CreateSuccesFullAuthentication(externalIdentity, userWithMatchingEmailClaim); }
/// <summary> /// Initializes a new instance of the <see cref="AuthenticateResult" /> class. This /// version of the constructor indicates a partial login (with a redirect) without /// knowledge of the subject claim. /// </summary> /// <param name="redirectPath">The redirect path. This should be relative to the /// current web server. The <c>"~/"</c> prefix is supported to allow application-relative /// paths to be used (e.g. "~/path"). /// </param> /// <param name="externalId">The external identifier that represents the external identity /// provider the partial login is created from. This will be re-presented to correlate the request /// when the user resumes from the redirect.</param> /// <exception cref="System.ArgumentNullException"> /// redirectPath /// or /// externalId /// </exception> /// <exception cref="System.ArgumentException">redirectPath must start with / or ~/</exception> public AuthenticateResult(string redirectPath, ExternalIdentity externalId) { if (redirectPath.IsMissing()) { throw new ArgumentNullException("redirectPath"); } if (!redirectPath.StartsWith("~/") && !redirectPath.StartsWith("/")) { throw new ArgumentException("redirectPath must start with / or ~/"); } if (externalId == null) { throw new ArgumentNullException("externalId"); } this.PartialSignInRedirectPath = redirectPath; var id = new ClaimsIdentity(externalId.Claims, Constants.PartialSignInAuthenticationType); // we're keeping the external provider info for the partial signin so we can re-execute AuthenticateExternalAsync // once the user is re-directed back into identityserver from the external redirect id.AddClaim(new Claim(Constants.ClaimTypes.ExternalProviderUserId, externalId.ProviderId, ClaimValueTypes.String, externalId.Provider)); User = new ClaimsPrincipal(id); }
private async Task<AuthenticateResult> AddExternalUserAsync(HttpClient client, ExternalIdentity identity) { using (var response = await client.PostAsync($"/api/users", new StringContent($"{{ id: \"{identity.ProviderId}\", password: \"password\" }}", Encoding.Unicode, "text/json"))) { if (!response.IsSuccessStatusCode) { return new AuthenticateResult($"Could not create new external user. Error code: {response.StatusCode}"); } } return new AuthenticateResult(identity.ProviderId, GetDisplayName(identity.Claims) ?? identity.ProviderId, identity.Claims); }
public async Task<IHttpActionResult> ResumeLoginFromRedirect(string resume) { Logger.Info("Callback requested to resume login from partial login"); if (resume.IsMissing()) { Logger.Error("no resumeId passed"); return RenderErrorPage(); } if (resume.Length > MaxSignInMessageLength) { Logger.Error("resumeId length longer than allowed length"); return RenderErrorPage(); } var user = await context.GetIdentityFromPartialSignIn(); if (user == null) { Logger.Error("no identity from partial login"); return RenderErrorPage(); } var type = GetClaimTypeForResumeId(resume); var resumeClaim = user.FindFirst(type); if (resumeClaim == null) { Logger.Error("no claim matching resumeId"); return RenderErrorPage(); } var signInId = resumeClaim.Value; if (signInId.IsMissing()) { Logger.Error("No signin id found in resume claim"); return RenderErrorPage(); } var signInMessage = signInMessageCookie.Read(signInId); if (signInMessage == null) { Logger.Error("No cookie matching signin id found"); return RenderErrorPage(); } AuthenticateResult result = null; // determine which return path the user is taking -- are they coming from // a ExternalProvider partial logon, or not var externalProviderClaim = user.FindFirst(Constants.ClaimTypes.ExternalProviderUserId); // cleanup the claims from the partial login if (user.HasClaim(c => c.Type == Constants.ClaimTypes.PartialLoginRestartUrl)) { user.RemoveClaim(user.FindFirst(Constants.ClaimTypes.PartialLoginRestartUrl)); } if (user.HasClaim(c => c.Type == Constants.ClaimTypes.PartialLoginReturnUrl)) { user.RemoveClaim(user.FindFirst(Constants.ClaimTypes.PartialLoginReturnUrl)); } if (user.HasClaim(c => c.Type == Constants.ClaimTypes.ExternalProviderUserId)) { user.RemoveClaim(user.FindFirst(Constants.ClaimTypes.ExternalProviderUserId)); } if (user.HasClaim(c => c.Type == GetClaimTypeForResumeId(resume))) { user.RemoveClaim(user.FindFirst(GetClaimTypeForResumeId(resume))); } if (externalProviderClaim != null) { Logger.Info("using ExternalProviderUserId to call AuthenticateExternalAsync"); var provider = externalProviderClaim.Issuer; var providerId = externalProviderClaim.Value; var externalIdentity = new ExternalIdentity { Provider = provider, ProviderId = providerId, Claims = user.Claims }; Logger.InfoFormat("external user provider: {0}, provider ID: {1}", externalIdentity.Provider, externalIdentity.ProviderId); var externalContext = new ExternalAuthenticationContext { ExternalIdentity = externalIdentity, SignInMessage = signInMessage }; await userService.AuthenticateExternalAsync(externalContext); result = externalContext.AuthenticateResult; if (result == null) { Logger.Warn("user service failed to authenticate external identity"); var msg = localizationService.GetMessage(MessageIds.NoMatchingExternalAccount); await eventService.RaiseExternalLoginFailureEventAsync(externalIdentity, signInId, signInMessage, msg); return await RenderLoginPage(signInMessage, signInId, msg); } if (result.IsError) { Logger.WarnFormat("user service returned error message: {0}", result.ErrorMessage); await eventService.RaiseExternalLoginFailureEventAsync(externalIdentity, signInId, signInMessage, result.ErrorMessage); return await RenderLoginPage(signInMessage, signInId, result.ErrorMessage); } Logger.Info("External identity successfully validated by user service"); await eventService.RaiseExternalLoginSuccessEventAsync(externalIdentity, signInId, signInMessage, result); } else { // check to see if the resultant user has all the claim types needed to login if (!Constants.AuthenticateResultClaimTypes.All(claimType => user.HasClaim(c => c.Type == claimType))) { Logger.Error("Missing AuthenticateResultClaimTypes -- rendering error page"); return RenderErrorPage(); } // this is a normal partial login continuation Logger.Info("Partial login resume success -- logging user in"); result = new AuthenticateResult(new ClaimsPrincipal(user)); await eventService.RaisePartialLoginCompleteEventAsync(result.User.Identities.First(), signInId, signInMessage); } return await SignInAndRedirectAsync(signInMessage, signInId, result); }
/// <summary> /// Initializes a new instance of the <see cref="AuthenticateResult" /> class. This /// version of the constructor indicates a partial login (with a redirect) without /// knowledge of the subject claim. /// </summary> /// <param name="redirectPath">The redirect path. This should be relative to the /// current web server. The <c>"~/"</c> prefix is supported to allow application-relative /// paths to be used (e.g. "~/path"). /// </param> /// <param name="externalId">The external identifier that represents the external identity /// provider the partial login is created from. This will be re-presented to correlate the request /// when the user resumes from the redirect.</param> /// <exception cref="System.ArgumentNullException"> /// redirectPath /// or /// externalId /// </exception> /// <exception cref="System.ArgumentException">redirectPath must start with / or ~/</exception> public AuthenticateResult(string redirectPath, ExternalIdentity externalId) { if (redirectPath.IsMissing()) throw new ArgumentNullException("redirectPath"); if (!redirectPath.StartsWith("~/") && !redirectPath.StartsWith("/")) { throw new ArgumentException("redirectPath must start with / or ~/"); } if (externalId == null) throw new ArgumentNullException("externalId"); this.PartialSignInRedirectPath = redirectPath; var id = new ClaimsIdentity(externalId.Claims, Constants.PartialSignInAuthenticationType); // we're keeping the external provider info for the partial signin so we can re-execute AuthenticateExternalAsync // once the user is re-directed back into identityserver from the external redirect id.AddClaim(new Claim(Constants.ClaimTypes.ExternalProviderUserId, externalId.ProviderId, ClaimValueTypes.String, externalId.Provider)); User = new ClaimsPrincipal(id); }
private static User ConfigureNewUser(ExternalIdentity externalIdentity) { var newUser = new User { Subject = Guid.NewGuid().ToString(), IsActive = true }; var userLogin = new UserLogin { Subject = newUser.Subject, LoginProvider = externalIdentity.Provider, ProviderKey = externalIdentity.ProviderId }; newUser.UserLogins.Add(userLogin); GetProfileClaimsFromIdentity(externalIdentity, newUser) .Union(CreateBasicPrivilegeForNewUser(newUser)) .ToList() .ForEach(newUser.UserClaims.Add); return newUser; }
private static AuthenticateResult CreateSuccesFullAuthentication(ExternalIdentity externalIdentity, User authenticatedUser) { return new AuthenticateResult( authenticatedUser.Subject, authenticatedUser.UserClaims.First(c => c.ClaimType == Constants.ClaimTypes.GivenName).ClaimValue, authenticatedUser.UserClaims.Select(uc => new Claim(uc.ClaimType, uc.ClaimValue)), authenticationMethod: Constants.AuthenticationMethods.External, identityProvider: externalIdentity.Provider); }
private static AuthenticateResult AskWindowsAuthenticatedUserForAdditionalInfo(ExternalIdentity externalIdentity) { return new AuthenticateResult("~/completeadditionalinformation", externalIdentity); }
private static IEnumerable<UserClaim> GetProfileClaimsFromIdentity(ExternalIdentity externalIdentity, User newUser) { return externalIdentity .Claims.Where(c => c.Type.ToLowerInvariant() == Constants.ClaimTypes.GivenName || c.Type.ToLowerInvariant() == Constants.ClaimTypes.FamilyName || c.Type.ToLowerInvariant() == Constants.ClaimTypes.Email) .Select(c => new UserClaim { Id = Guid.NewGuid().ToString(), Subject = newUser.Subject, ClaimType = c.Type.ToLowerInvariant(), ClaimValue = c.Value }); }