Base solution for your next web application
Open Closed

Unable to implement Azure AD Microsoft Login in aspnetzero mvc/jquery project #11609


User avatar
0
omkarchoudhari created

Hello ,

I am using MVC Jquery Template (Version 12.0.0) and Xamarin for Android. I have a Microsoft login button on the Android app (registered in Azure). This is a single Tenant application - only with Default Tenant. When I click on it:

  1. it takes me to the Microsoft login page (code written in LoginViewModel.cs)

  2. I log in with my company credentials and get redirected back to my app.

  3. Then I set the values for my model and then a method gets called (_accountService.LoginUserMicrosoftAsync).

     var myUri = new Uri(ApplicationSetting.TenantUrl, UriKind.Absolute);
     PCA = PublicClientApplicationBuilder.Create(ApplicationSetting.ClientID)
             .WithIosKeychainSecurityGroup(ApplicationSetting.TenantId)
             .WithAuthority(myUri)
             .WithRedirectUri(ApplicationSetting.RedirectUrl)
             .Build();
    
     private async Task RegisterMicrosoftAsync()
     {
         try
         {
             var authResult = await PCA.AcquireTokenInteractive(ApplicationSetting.Scopes)
                             .WithParentActivityOrWindow(App.ParentWindow)
                             .WithUseEmbeddedWebView(true)
                             .ExecuteAsync();
    
             //#1. External Authentication Model
             _accountService.CustomExternalAuthenticateModel.UserNameOrEmailAddress = authResult.Account.Username;
             _accountService.CustomExternalAuthenticateModel.ProviderKey = authResult.UniqueId;
             _accountService.CustomExternalAuthenticateModel.ProviderAccessCode = authResult.AccessToken;
    
             //Getting All Claims From Access Token
             claims = authResult.ClaimsPrincipal.Claims;
             foreach (var claim in claims)
             {
                 if (claim.Type == "iss")
                 {
                     _accountService.CustomExternalAuthenticateModel.AuthProvider = claim.Issuer;
                     break;
                 }
             }
    
             //#2. Authenticate Model
             _accountService.AbpAuthenticateModel.UserNameOrEmailAddress = authResult.Account.Username;
             _accountService.AbpAuthenticateModel.Password = "123qwe";
    
             //MessagingCenter.Send<String>(authResult.Account.Username, "MicrosoftMail");
    
             await SetBusyAsync(async () =>
             {
                 await _accountService.LoginUserMicrosoftAsync();
             });
         }
         catch (Exception exp)
         {
             await Application.Current.MainPage.DisplayAlert("Alert",
                 "Invalid Login Credentials, please try again.", "Ok");
             Console.WriteLine(exp.Message);
         }
     }
    
  4. (_accountService.LoginUserMicrosoftAsync) This method in turn calls a method (_accessTokenManager.LoginMicrosoftAsync).

     public async Task LoginUserMicrosoftAsync()
     {
         await WebRequestExecuter.Execute(_accessTokenManager.LoginMicrosoftAsync, ExternalAuthenticateSucceed, ex => Task.CompletedTask);
     }
    
     public async Task<CustomExternalAuthenticateResultModel> LoginMicrosoftAsync()
     {
         EnsureAccessTokenProvided();
    
         using (var client = CreateApiClient())
         {
             if (_externalAuthenticateModel.ProviderAccessCode != null)
             {
                 client.WithHeader(_multiTenancyConfig.TenantIdResolveKey, _applicationContext.CurrentTenant.TenantId).WithOAuthBearerToken(_externalAuthenticateModel.ProviderAccessCode);
             }
    
             var response = await client
                 .Request(LoginUrlSegmentMicrosoft) //LoginUrlSegmentMicrosoft = "api/TokenAuth/ExternalAuthenticate"
                 .PostJsonAsync(_externalAuthenticateModel) //externalAuthenticateModel model had exact properties like ExternalAuthenticateModel
                 .ReceiveJson<AjaxResponse<CustomExternalAuthenticateResultModel>>(); //CustomExternalAuthenticateResultModel model has exact properties like ExternalAuthenticateResultModel
    
             if (!response.Success || response.Result == null)
             {
                 ExternalAuthenticateResult = null;
                 throw new UserFriendlyException(response.Error.Message + ": " + response.Error.Details);
             }
    
             ExternalAuthenticateResult = response.Result;
             ExternalAuthenticateResult.RefreshTokenExpireInSeconds = DateTime.Now.Add(AppConsts.RefreshTokenExpiration).Second;
    
             return ExternalAuthenticateResult;
         }
     }
    
  5. This method sends a request To API; in TokenAuthController (api/TokenAuth/ExternalAuthenticate).

  6. So, when this API is hit, the method, var externalUser = await GetExternalUserInfo(model) throws an exception/errors out, saying that Unknown External Auth Provider https://login.microsoftonline.com/{Tenant Id Of different organizations}/v2.0.

I tried every setting, even by setting the Auth Provider exactly the same as I am getting (in appsettings), but it didn’t work. I tried setting AuthProvider as Microsoft also, but this is not working. If I comment var externalUser = await GetExternalUserInfo(model) method, then the login works fine (as the user is already registered.). But with this method uncommented, it always checks user info and errors out. I want functionality that when a user tries to log in through different Microsoft accounts (e.g. Outlook, hotmail, etc.), and if a user doesn't exist then it should register it.

Please help urgently. Thanks in advance.


3 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @omkarchoudhari

    Your flow seems correct. Is it possible to share the full error message and JSON payload you are sending to TokenAuthController (api/TokenAuth/ExternalAuthenticate) ?

  • User Avatar
    0
    omkarchoudhari created

    Hello @ismcagdas,

    This issue is now resolved.

    What did we do to resolve this issue ?

    1. If we are using Microsoft and Google Login (on the web). Then we will already have consumer secrets, secret Id, etc. configurations in our MVC. Just copy them and paste them into Web.Host project's appsettings.

    2. Auth Provider for Microsoft will be Microsoft, for Google will be Google, etc. (Please provide Hard coded values for Auth providers)

    3. when you run the project and receive auth token, values will be set to particular properties (from the custom model- create CustomExternalAuthenticateModel just like ExternalAuthenticateModel in TokenAuthController , and also create CustomExternalAuthenticateResultModel just like ExternalAuthenticateResultModel in TokenAuthController Create these files in Application.Client project) and it sends a request to the ExternalAuthenticate method.

    4. And rest is done by ANZ itself (it takes us to the main page - the flyout page).

    Note: Just follow the normal Login flow and create the required models, classes, etc. To run project locally, run two VS instances. On one VS instance run Droid project and on the other run Web.Host (not IIS server profile, run Mobile) in other VS.

    Hope this is helpful to someone who faces similar issue.

    Thanks for your support

  • User Avatar
    0
    ismcagdas created
    Support Team

    Thanks a lot @omkarchoudhari for sharing this.