Мобильное приложение Мауи, используйте токен от msal, чтобы получить одиночную палатку и sp.list
У меня есть мобильное приложение Maui, и я использую msal для аутентификации. это работает нормально, и для входа в систему отображается экран входа в систему. после входа в систему я загружу новую страницу для вызова sp.list.
например. https://company.sharepont.com/sites/[имя сайта] /Lists/getbytitle('nameoflist')/items.
Все больше дней я искал предложения о том, как использовать httpclient с автотокеном, и заметил, что работа идет.
я использую это репо. https://github.com/carlfranklin/MsalAuthInMaui
Теперь, как использовать токен в процессе входа в систему для получения информации с sp.site. с httpclient или лучше с графиком.
Этот код не работает.
using MAUI.MSALClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System.Net.Http.Headers;
using System.Reflection;
using CommunityToolkit.Maui;
namespace ESR_Mobile_app.Views
{
public partial class Rapport : ContentPage
{
public Rapport()
{
InitializeComponent();
IAccount cachedUserAccount = Task.Run(async () => await PublicClientSingleton.Instance.MSALClientHelper.FetchSignedInUserFromCache()).Result;
_ = Dispatcher.DispatchAsync(async () =>
{
if (cachedUserAccount == null)
{
await Shell.Current.GoToAsync("start");
}
});
}
private static async Task<string>CallWebAPIWithToken(AuthenticationResult authResult)
{
try
{
//get data from API
HttpClient client = new HttpClient();
// create the request
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://company.sharepoint.com/sites/Projekte/_api/Lists/getbytitle('some_list')/items");
// ** Add Authorization Header **
message.Headers.Add("Authorization", authResult.CreateAuthorizationHeader());
// send the request and return the response
HttpResponseMessage response = await client.SendAsync(message).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
//return responseString;
Console.WriteLine(responseString);
}
//catch (Exception ex)
catch (HttpRequestException e)
{
//return ex.ToString();
Console.WriteLine("\nException Caught!");
Console.WriteLine("Message :{0} ", e.Message);
}
}
private async Task ShowMessage(string title, string message)
{
_ = this.Dispatcher.Dispatch(async () =>
{
await DisplayAlert(title, message, "OK").ConfigureAwait(false);
});
}
}
}
Помощник MsalClient
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.IdentityModel.Abstractions;
using System.Diagnostics;
namespace MAUI.MSALClient
{
/// <summary>
/// Contains methods that initialize and use the MSAL SDK
/// </summary>
public class MSALClientHelper
{
/// <summary>
/// As for the Tenant, you can use a name as obtained from the azure portal, e.g. kko365.onmicrosoft.com"
/// </summary>
public AzureADConfig AzureADConfig;
/// <summary>
/// Gets the authentication result (if available) from MSAL's various operations.
/// </summary>
/// <value>
/// The authentication result.
/// </value>
public AuthenticationResult AuthResult { get; private set; }
/// <summary>
/// Gets a value indicating whether this instance of PublicClientApp was initialized with a broker .
/// </summary>
/// <value>
/// <c>true</c> if this instance is broker initialized; otherwise, <c>false</c>.
/// </value>
public bool IsBrokerInitialized { get; private set; }
/// <summary>
/// Gets the MSAL public client application instance.
/// </summary>
/// <value>
/// The public client application.
/// </value>
public IPublicClientApplication PublicClientApplication { get; private set; }
/// <summary>
/// This will determine if the Interactive Authentication should be Embedded or System view
/// </summary>
public bool UseEmbedded { get; set; } = false;
/// <summary>
/// The PublicClientApplication builder used internally
/// </summary>
private PublicClientApplicationBuilder PublicClientApplicationBuilder;
// Token Caching setup - Mac
public static readonly string KeyChainServiceName = "Contoso.MyProduct";
public static readonly string KeyChainAccountName = "MSALCache";
// Token Caching setup - Linux
public static readonly string LinuxKeyRingSchema = "com.contoso.msaltokencache";
public static readonly string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection;
public static readonly string LinuxKeyRingLabel = "MSAL token cache for Contoso.";
public static readonly KeyValuePair<string, string> LinuxKeyRingAttr1 = new KeyValuePair<string, string>("Version", "1");
public static readonly KeyValuePair<string, string> LinuxKeyRingAttr2 = new KeyValuePair<string, string>("ProductGroup", "Contoso");
private static string PCANotInitializedExceptionMessage = "The PublicClientApplication needs to be initialized before calling this method. Use InitializePublicClientAppAsync() or InitializePublicClientAppForWAMBrokerAsync() to initialize.";
/// <summary>
/// Initializes a new instance of the <see cref="MSALClientHelper"/> class.
/// </summary>
public MSALClientHelper(AzureADConfig azureADConfig)
{
AzureADConfig = azureADConfig;
this.InitializePublicClientApplicationBuilder();
}
/// <summary>
/// Initializes the MSAL's PublicClientApplication builder from config.
/// </summary>
/// <autogeneratedoc />
private void InitializePublicClientApplicationBuilder()
{
this.PublicClientApplicationBuilder = PublicClientApplicationBuilder.Create(AzureADConfig.ClientId)
.WithAuthority(string.Format(AzureADConfig.Authority, AzureADConfig.TenantId))
.WithExperimentalFeatures() // this is for upcoming logger
.WithLogging(new IdentityLogger(EventLogLevel.Warning), enablePiiLogging: false) // This is the currently recommended way to log MSAL message. For more info refer to https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/logging. Set Identity Logging level to Warning which is a middle ground
.WithClientCapabilities(new string[] { "cp1" }) // declare this client app capable of receiving CAE events- https://aka.ms/clientcae
.WithIosKeychainSecurityGroup("com.microsoft.adalcache");
}
/// <summary>
/// Initializes the public client application of MSAL.NET with the required information to correctly authenticate the user.
/// </summary>
/// <returns></returns>
public async Task<IAccount> InitializePublicClientAppAsync()
{
// Initialize the MSAL library by building a public client application
this.PublicClientApplication = this.PublicClientApplicationBuilder
.WithRedirectUri(PlatformConfig.Instance.RedirectUri) // redirect URI is set later in PlatformConfig when the platform has been decided
.Build();
await AttachTokenCache();
return await FetchSignedInUserFromCache().ConfigureAwait(false);
}
/// <summary>
/// Initializes the public client application of MSAL.NET with the required information to correctly authenticate the user using the WAM broker.
/// </summary>
/// <returns>An IAccount of an already signed-in user (if available)</returns>
public async Task<IAccount> InitializePublicClientAppForWAMBrokerAsync()
{
// Initialize the MSAL library by building a public client application
this.PublicClientApplication = this.PublicClientApplicationBuilder
.WithRedirectUri(PlatformConfig.Instance.RedirectUri) // redirect URI is set later in PlatformConfig when the platform is decided
.WithBroker()
.WithParentActivityOrWindow(() => PlatformConfig.Instance.ParentWindow) // This is required when using the WAM broker and is set later in PlatformConfig when the platform has been decided
.Build();
this.IsBrokerInitialized = true;
await AttachTokenCache();
return await FetchSignedInUserFromCache().ConfigureAwait(false);
}
/// <summary>
/// Attaches the token cache to the Public Client app.
/// </summary>
/// <returns>IAccount list of already signed-in users (if available)</returns>
private async Task<IEnumerable<IAccount>> AttachTokenCache()
{
if (DeviceInfo.Current.Platform != DevicePlatform.WinUI)
{
return null;
}
// Cache configuration and hook-up to public application. Refer to https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache#configuring-the-token-cache
var storageProperties = new StorageCreationPropertiesBuilder(AzureADConfig.CacheFileName, AzureADConfig.CacheDir)
.Build();
var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
msalcachehelper.RegisterCache(PublicClientApplication.UserTokenCache);
// If the cache file is being reused, we'd find some already-signed-in accounts
return await PublicClientApplication.GetAccountsAsync().ConfigureAwait(false);
}
/// <summary>
/// Signs in the user and obtains an Access token for a provided set of scopes
/// </summary>
/// <param name="scopes"></param>
/// <returns> Access Token</returns>
public async Task<string> SignInUserAndAcquireAccessToken(string[] scopes)
{
Exception<NullReferenceException>.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);
var existingUser = await FetchSignedInUserFromCache().ConfigureAwait(false);
try
{
// 1. Try to sign-in the previously signed-in account
if (existingUser != null)
{
this.AuthResult = await this.PublicClientApplication
.AcquireTokenSilent(scopes, existingUser)
.ExecuteAsync()
.ConfigureAwait(false);
}
else
{
if (this.IsBrokerInitialized)
{
Console.WriteLine("No accounts found in the cache. Trying Window's default account.");
this.AuthResult = await this.PublicClientApplication
.AcquireTokenSilent(scopes, Microsoft.Identity.Client.PublicClientApplication.OperatingSystemAccount)
.ExecuteAsync()
.ConfigureAwait(false);
}
else
{
this.AuthResult = await SignInUserInteractivelyAsync(scopes);
}
}
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenInteractive to acquire a token interactively
Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
this.AuthResult = await this.PublicClientApplication
.AcquireTokenInteractive(scopes)
.WithLoginHint(existingUser?.Username ?? String.Empty)
.ExecuteAsync()
.ConfigureAwait(false);
}
catch (MsalException msalEx)
{
Debug.WriteLine($"Error Acquiring Token interactively:{Environment.NewLine}{msalEx}");
throw msalEx;
}
return this.AuthResult.AccessToken;
}
/// <summary>
/// Signs the in user and acquire access token for a provided set of scopes.
/// </summary>
/// <param name="scopes">The scopes.</param>
/// <param name="extraclaims">The extra claims, usually from CAE. We basically handle CAE by sending the user back to Azure AD for
/// additional processing and requesting a new access token for Graph</param>
/// <returns></returns>
public async Task<String> SignInUserAndAcquireAccessToken(string[] scopes, string extraclaims)
{
Exception<NullReferenceException>.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);
try
{
// Send the user to Azure AD for re-authentication as a silent acquisition wont resolve any CAE scenarios like an extra claims request
this.AuthResult = await this.PublicClientApplication.AcquireTokenInteractive(scopes)
.WithClaims(extraclaims)
.ExecuteAsync()
.ConfigureAwait(false);
}
catch (MsalException msalEx)
{
Debug.WriteLine($"Error Acquiring Token:{Environment.NewLine}{msalEx}");
}
return this.AuthResult.AccessToken;
}
/// <summary>
/// Shows a pattern to sign-in a user interactively in applications that are input constrained and would need to fall-back on device code flow.
/// </summary>
/// <param name="scopes">The scopes.</param>
/// <param name="existingAccount">The existing account.</param>
/// <returns></returns>
public async Task<AuthenticationResult> SignInUserInteractivelyAsync(string[] scopes, IAccount existingAccount = null)
{
Exception<NullReferenceException>.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);
if (this.PublicClientApplication == null)
throw new NullReferenceException();
// If the operating system has UI
if (this.PublicClientApplication.IsUserInteractive())
{
if (PublicClientSingleton.Instance.UseEmbedded)
{
return await this.PublicClientApplication.AcquireTokenInteractive(scopes)
.WithLoginHint(existingAccount?.Username ?? String.Empty)
.WithUseEmbeddedWebView(true)
.WithParentActivityOrWindow(PlatformConfig.Instance.ParentWindow)
.ExecuteAsync()
.ConfigureAwait(false);
}
else
{
SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions();
#if IOS
// Hide the privacy prompt in iOS
systemWebViewOptions.iOSHidePrivacyPrompt = true;
#endif
return await this.PublicClientApplication.AcquireTokenInteractive(scopes)
.WithLoginHint(existingAccount?.Username ?? String.Empty)
.WithSystemWebViewOptions(systemWebViewOptions)
.WithParentActivityOrWindow(PlatformConfig.Instance.ParentWindow)
.ExecuteAsync()
.ConfigureAwait(false);
}
}
// If the operating system does not have UI (e.g. SSH into Linux), you can fallback to device code, however this
// flow will not satisfy the "device is managed" CA policy.
return await this.PublicClientApplication.AcquireTokenWithDeviceCode(scopes, (dcr) =>
{
Console.WriteLine(dcr.Message);
return Task.CompletedTask;
}).ExecuteAsync().ConfigureAwait(false);
}
/// <summary>
/// Removes the first signed-in user's record from token cache
/// </summary>
public async Task SignOutUserAsync()
{
var existingUser = await FetchSignedInUserFromCache().ConfigureAwait(false);
await this.SignOutUserAsync(existingUser).ConfigureAwait(false);
}
/// <summary>
/// Removes a given user's record from token cache
/// </summary>
/// <param name="user">The user.</param>
public async Task SignOutUserAsync(IAccount user)
{
if (this.PublicClientApplication == null) return;
await this.PublicClientApplication.RemoveAsync(user).ConfigureAwait(false);
}
/// <summary>
/// Fetches the signed in user from MSAL's token cache (if available).
/// </summary>
/// <returns></returns>
public async Task<IAccount> FetchSignedInUserFromCache()
{
Exception<NullReferenceException>.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);
// get accounts from cache
IEnumerable<IAccount> accounts = await this.PublicClientApplication.GetAccountsAsync().ConfigureAwait(false);
// Error corner case: we should always have 0 or 1 accounts, not expecting > 1
// This is just an example of how to resolve this ambiguity, which can arise if more apps share a token cache.
// Note that some apps prefer to use a random account from the cache.
if (accounts.Count() > 1)
{
foreach (var acc in accounts)
{
await this.PublicClientApplication.RemoveAsync(acc);
}
return null;
}
return accounts.SingleOrDefault();
}
}
}