Forum Xamarin.Android

MSAL Authentication Does Not Complete

mcconnelljhmcconnelljh USMember ✭✭

I'm trying to get my feet wet with Xamarin and I'm having trouble adding in my organization's login. The screen shot below is as far as I can get attempting to login. When I click "Continue" the same page just loads again. Not really sure what's going on.

The image is the screen I'm stuck on.

I've added code that represents the app class and the code behind for the XAML page attempting to login, leaving out what I "think" is irrelevant.

Any suggestions?

App class...

public partial class App : Application
     {
         public static string AzureBackendUrl =
             DeviceInfo.Platform == DevicePlatform.Android ? "http://10.0.2.2:5000" : "http://localhost:5000";
         public static bool UseMockDataStore = true;
         public static IPublicClientApplication PCA = null;
         public static string ClientID = "CLIENT_ID";
         public static string[] Scopes = { "User.Read" };
         public static string Username = string.Empty;
         public static object ParentWindow { get; set; }
         public App()
         {
             InitializeComponent();
             if (UseMockDataStore)
                 DependencyService.Register<MockDataStore>();
             else
                 DependencyService.Register<AzureDataStore>(); 
             PCA = PublicClientApplicationBuilder.Create(ClientID)
                 .WithRedirectUri($"msal{App.ClientID}://auth")
                 .Build();
             MainPage = new MSAL_Example();
         }
     }

MSAL_Example class...

public partial class MSAL_Example : ContentPage
 {
     public static string tenant_name = "MY_TENANT_NAME"; 
     public MSAL_Example()
     {
         InitializeComponent();
         App.ParentWindow = this;
     }
     public async Task SignOutAsync()
     {
         IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync();
         try
         {
             while (accounts.Any())
             {
                 await App.PCA.RemoveAsync(accounts.FirstOrDefault());
                 accounts = await App.PCA.GetAccountsAsync();
             }
             slUser.IsVisible = false;
             Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign in"; });
         }
         catch (Exception ex)
         {
             Debug.WriteLine("\tERROR {0}", ex.Message);
         }
     }
     public async Task SignInAsync()
     {
         AuthenticationResult authResult = null;
         IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync(); 
         // let's see if we have a user in our belly already
         try
         {
             IAccount firstAccount = accounts.FirstOrDefault();
             authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount)
                                   .ExecuteAsync();
             await RefreshUserDataAsync(authResult.AccessToken).ConfigureAwait(false);
             Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign out"; });
         }
         catch (MsalUiRequiredException ex)
         {
             try
             {
                 authResult = await App.PCA.AcquireTokenInteractive(App.Scopes)
                                           .WithParentActivityOrWindow(App.ParentWindow)
                                           .WithAuthority("https://login.microsoftonline.com/" + tenant_name)
                                           .ExecuteAsync();

                 await RefreshUserDataAsync(authResult.AccessToken);
                 Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign out"; });
             }
             catch (Exception ex2)
             {
                 Debug.WriteLine("\tERROR {0}", ex2.Message);
             }
         }
     } 
     public async Task RefreshUserDataAsync(string token)
     {
         //get data from API
         HttpClient client = new HttpClient();
         HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
         message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);
         HttpResponseMessage response = await client.SendAsync(message);
         string responseString = await response.Content.ReadAsStringAsync();
         if (response.IsSuccessStatusCode)
         {
             JObject user = JObject.Parse(responseString); 
             slUser.IsVisible = true; 
             Device.BeginInvokeOnMainThread(() =>
             {

                 lblDisplayName.Text = user["displayName"].ToString();
                 lblGivenName.Text = user["givenName"].ToString();
                 lblId.Text = user["id"].ToString();
                 lblSurname.Text = user["surname"].ToString();
                 lblUserPrincipalName.Text = user["userPrincipalName"].ToString();

                 // just in case
                 btnSignInSignOut.Text = "Sign out";
             });
         }
         else
         {
             await DisplayAlert("Something went wrong with the API call", responseString, "Dismiss");
         }
     }
 }

Answers

  • AndyFlisherAndyFlisher GBBeta, University ✭✭✭✭

    Is this using Azure ADB2C or similar? In which case the redirect URL is set at the other end in the App Registration you have setup in AD. For example in one of mine I have a redirect url of;

    msal1111111111-2222-3333-4444-55555555://auth. where the long number is the App Registration ID, sometimes called the client ID

  • mcconnelljhmcconnelljh USMember ✭✭

    I have set the redirect URL in Azure..

    And in my code I have it set in the App class...

    PCA = PublicClientApplicationBuilder.Create(ClientID)
                     .WithRedirectUri($"msal{App.ClientID}://auth")
                     .Build();
    

    I believe this is what you're talking about. Do I need to be doing something else with the url?

  • AndyFlisherAndyFlisher GBBeta, University ✭✭✭✭

    Looks about right, in my example I have in App.xaml.cs;

    AuthenticationClient = PublicClientApplicationBuilder.Create(SettingsViewModel.AadClientId)
                    .WithB2CAuthority(SettingsViewModel.AadSignInPolicyUrl)
                    .WithRedirectUri("msal" + SettingsViewModel.AadClientId + "://auth")
                    .Build();
    

    Then in my loginpage.xaml.cs in OnAppearing();

    try
                {
                    // Look for existing account
                    var accounts = await App.AuthenticationClient.GetAccountsAsync();
    
                    //Logout
                    while (accounts.Any())
                    {
                        await App.AuthenticationClient.RemoveAsync(accounts.First());
                        accounts = await App.AuthenticationClient.GetAccountsAsync();
                    }
    
                    var result = await App.AuthenticationClient
                        .AcquireTokenSilent(SettingsViewModel.AadScopes, accounts.FirstOrDefault())
                        .ExecuteAsync();
                }
                catch
                {
                    // Do nothing - the user isn't logged in
                }
    

    And also within my login button .Clicked command handler;

    AuthenticationResult result;
                    result = await App.AuthenticationClient
                        .AcquireTokenInteractive(SettingsViewModel.AadScopes)
                        .WithPrompt(Prompt.SelectAccount)
                        .WithParentActivityOrWindow(App.UIParent)
                        .ExecuteAsync();
    
                    var jwt = new JwtSecurityToken(result.IdToken);
    

    At the point we get to the last line it's done it's redirect, authenticated, and redirected back

  • ryanlafountainryanlafountain Member ✭✭

    Not sure if you are still stuggling on this but in the main activity you have to set the contiunation helper. Let me know if you need it and I can provide the code

  • mcconnelljhmcconnelljh USMember ✭✭
    @ryanlafountain I am still stuck on this,and I would appreciate any code or help you can offer.
  • ryanlafountainryanlafountain Member ✭✭

    @mcconnelljh , can you post where the SignInAsync is being called from? Also, can you post the MainActivity.cs OnActivityResult override code. Can you also post the AndroidManifest.xml.

  • Aston13Aston13 Member

    I am having the same issue, did you find the solution in the end?

Sign In or Register to comment.