- ABP Framework version: v7.3.2
- UI Type: Blazor Server
- Database System: EF Core (SQL Server)
- Tiered (for MVC) or Auth Server Separated (for Angular): no
I use the login with the external provider "Microsoft" (Azure AD). If the user does not exist yet, it will be registered with the email address by default. However, I would like to automatically fill the other information as well: firstname, lastname and profile picture...
How can I do this? I have tried with a custom LoginModel but have not figured out how and which method I would need to hook into.
7 Answer(s)
-
0
hi
You should override the
OnGetExternalLoginCallbackAsync
method. -
0
I see that I can retrieve the claims here.
public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) { var loginInfo = await SignInManager.GetExternalLoginInfoAsync(); var claims = loginInfo.Principal.Claims; return await base.OnGetExternalLoginCallbackAsync(returnUrl, returnUrlHash, remoteError); }
But then comes the page for registration, in which only the mail address is added to the new user. How can I add this additional information there? So that at the end the new user is completed with data from the claims?
-
0
-
0
I have now solved this a little differently, as I do not need the manual registration and prefer to create the user automatically. Therefore I have now copied the OnGetExternalLoginCallbackAsync method and adapted it accordingly (see below).
loginInfo.Principal.AddClaim(AbpClaimTypes.SurName, loginInfo.Principal.FindFirstValue(ClaimTypes.Surname) ?? string.Empty);
public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) { if (remoteError != null) { Logger.LogWarning($"External login callback error: {remoteError}"); return RedirectToPage("./Login"); } await IdentityOptions.SetAsync(); var loginInfo = await SignInManager.GetExternalLoginInfoAsync(); if (loginInfo == null) { Logger.LogWarning("External login info is not available"); return RedirectToPage("./Login"); } var result = await SignInManager.ExternalLoginSignInAsync( loginInfo.LoginProvider, loginInfo.ProviderKey, isPersistent: false, bypassTwoFactor: true ); if (!result.Succeeded) { await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, Action = "Login" + result }); } if (result.IsLockedOut) { Logger.LogWarning($"External login callback error: user is locked out!"); throw new UserFriendlyException("Cannot proceed because user is locked out!"); } if (result.IsNotAllowed) { Logger.LogWarning($"External login callback error: user is not allowed!"); throw new UserFriendlyException("Cannot proceed because user is not allowed!"); } if (result.Succeeded) { return RedirectSafely(returnUrl, returnUrlHash); } //TODO: Handle other cases for result! var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email); if (string.IsNullOrWhiteSpace(email)) { return RedirectToPage("./Register", new { IsExternalLogin = true, ExternalLoginAuthSchema = loginInfo.LoginProvider, ReturnUrl = returnUrl }); } var user = await UserManager.FindByEmailAsync(email); if (user == null) { // Get additional infos from AzureAD Claims and set them to AbpClaims loginInfo.Principal.AddClaim(AbpClaimTypes.Email, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty); loginInfo.Principal.AddClaim(AbpClaimTypes.UserName, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty); loginInfo.Principal.AddClaim(AbpClaimTypes.Name, loginInfo.Principal.FindFirstValue(ClaimTypes.GivenName) ?? string.Empty); loginInfo.Principal.AddClaim(AbpClaimTypes.SurName, loginInfo.Principal.FindFirstValue(ClaimTypes.Surname) ?? string.Empty); user = await CreateExternalUserAsync(loginInfo); } else { if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null) { CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo)); } } await SignInManager.SignInAsync(user, false); await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, Action = result.ToIdentitySecurityLogAction(), UserName = user.Name }); return RedirectSafely(returnUrl, returnUrlHash); }
How do I get the profile picture now? I think the only way is through the Microsoft Graph, right? Can you tell me how to create the GraphServiceClient? I don't know what I need to pass there as parameters and have available in this method.
-
0
hi
Sorry, I'm not familiar with Microsoft Graph.
-
0
Ok, I have done it now. I'll post the code in case anyone else can use it.
I find it a bit uncomfortable that I had to implement an extra class for the AuthenticationProvider with interface IAccessTokenProvider. @maliming, do you know a more elegant solution?
public class MyAuthenticationProvider : IAccessTokenProvider { private readonly AuthenticationToken _accessToken; public AllowedHostsValidator AllowedHostsValidator { get; } public MyAuthenticationProvider(AuthenticationToken accessToken) { _accessToken = accessToken; } public Task<string> GetAuthorizationTokenAsync(Uri uri, Dictionary<string, object>? additionalAuthenticationContext = null, CancellationToken cancellationToken = default) { return Task.FromResult(_accessToken.Value); } }
In class MyLoginModel.cs:
public override async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) { ... var externalUser = await UserManager.FindByEmailAsync(email); if (externalUser == null) { // Create a new user with data from AzureAD profile loginInfo.Principal.AddClaim(AbpClaimTypes.Email, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty); loginInfo.Principal.AddClaim(AbpClaimTypes.UserName, loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? string.Empty); loginInfo.Principal.AddClaim(AbpClaimTypes.Name, loginInfo.Principal.FindFirstValue(ClaimTypes.GivenName) ?? string.Empty); loginInfo.Principal.AddClaim(AbpClaimTypes.SurName, loginInfo.Principal.FindFirstValue(ClaimTypes.Surname) ?? string.Empty); externalUser = await CreateExternalUserAsync(loginInfo); await SetProfilePictureAsync(loginInfo, externalUser); } ... }
/// < summary> /// Save user's profile picture from AzureAD as ABP profile picture. /// Note: Code copied and modified from Volo.Abp.Account.AccountAppService /// < /summary> private async Task SetProfilePictureAsync(ExternalLoginInfo loginInfo, IdentityUser user) { try { var accessToken = loginInfo.AuthenticationTokens?.FirstOrDefault(x => x.Name == "access_token"); if (accessToken == null) { return; } var authenticationProvider = new BaseBearerTokenAuthenticationProvider(new MyAuthenticationProvider(accessToken)); var graphClient = new GraphServiceClient(authenticationProvider); // Get user photo from Azure using (var photoStream = await graphClient.Me.Photo.Content.GetAsync()) { if (photoStream == null) { return; } var input = new ProfilePictureInput { ImageContent = new RemoteStreamContent(photoStream), Type = ProfilePictureType.Image }; await SettingManager.SetForUserAsync(user.Id, AccountSettingNames.ProfilePictureSource, input.Type.ToString()); var userIdText = user.Id.ToString(); if (input.Type != ProfilePictureType.Image) { if (await AccountProfilePictureContainer.ExistsAsync(userIdText)) { await AccountProfilePictureContainer.DeleteAsync(userIdText); } } else { if (input.ImageContent == null) { throw new NoImageProvidedException(); } await AccountProfilePictureContainer.SaveAsync(userIdText, input.ImageContent.GetStream(), true); } } } catch (Exception ex) { Logger.LogWarning($"Exception when retrieving or storing the profile picture: {ex.Message}"); } }
-
0
I think these code are fine. : )