بهروزرسانی: ۲۸ آگوست ۲۰۲۶
احراز هویت با MSAL و Microsoft Entra ID در .NET MAUI یعنی استفاده از کتابخانهی Microsoft.Identity.Client برای گرفتن توکن دسترسی از Entra ID (نام جدید Azure AD) با جریان Authorization Code + PKCE، ذخیرهسازی امن توکن در کش پلتفرم و تمدید سکوت (Silent Refresh) در دفعات بعدی. در MAUI 9 و بالاتر، MSAL از سه پلتفرم iOS، اندروید و ویندوز بهصورت یکپارچه پشتیبانی میکند و برای رولاوتهای سازمانی، فعالسازی Broker (Authenticator یا WAM) عملاً اجباری است.
MSAL چیست و چرا برای MAUI ضروری است؟
Microsoft Authentication Library (MSAL) کتابخانهی رسمی مایکروسافت برای گرفتن توکن OAuth 2.0 و OpenID Connect از Microsoft Entra ID، حسابهای شخصی مایکروسافت و مستأجران External ID است. برخلاف ADAL که در ژوئن ۲۰۲۳ به پایان پشتیبانی رسید، MSAL از جریان امن Authorization Code + PKCE استفاده میکند و برخلاف پیادهسازیهای دستی OAuth، منطق پیچیدهی مدیریت refresh token، کش، امضای PoP و Continuous Access Evaluation را داخل خود کپسوله میکند. صادقانه بگویم، بار اولی که سعی کردم OAuth را «دستی» در یک اپ Xamarin.Forms پیاده کنم، سه هفته وقتم را روی edge caseهای refresh token هدر دادم. بعد از آن دیگر هیچوقت به این ایده برنگشتم.
در .NET MAUI 9 و بالاتر، بستهی Microsoft.Identity.Client با هر سه پلتفرم iOS، اندروید و WinUI سازگار است و از پروژهی TFM چندگانهی MAUI بهصورت شفاف پشتیبانی میکند. اهمیت واقعی MSAL در اپهای سازمانی (که در تجربهی من رولاوتهای MAUI برای صنایع تحت رگولاتوری اغلب همین دستهاند) از سه چیز میآید: پشتیبانی Broker برای اجرای سیاستهای Conditional Access، مدیریت خودکار انقضای توکن، و SSO یکپارچه با Outlook، Teams و سایر اپهای مایکروسافت روی همان دستگاه. اگر میخواهید یک نمای کلی از سایر روشهای احراز هویت داشته باشید، مقالهی احراز هویت JWT در .NET MAUI رویکرد سبکتری را برای بکاندهای سفارشی پوشش میدهد.
آمادهسازی: ثبت اپلیکیشن در Microsoft Entra ID
پیش از نوشتن حتی یک خط کد، باید اپلیکیشن را در پرتال Entra ID ثبت کنید تا Client ID، Tenant ID و Redirect URIهای مجاز را دریافت کنید. به پرتال Microsoft Entra بروید، از منوی چپ App registrations را باز کنید و روی New registration کلیک کنید. یک نام برای اپلیکیشن انتخاب کنید و در بخش Supported account types بسته به نیاز خود، «تنها این مستأجر»، «چند مستأجره» یا «حسابهای شخصی + کاری» را برگزینید.
در قسمت Redirect URI، پلتفرم Public client/native (mobile & desktop) را انتخاب کنید. سه URI زیر را اضافه کنید:
msal{CLIENT_ID}://authبرای iOS و اندروید.http://localhostبرای WinUI بدون Broker.ms-appx-web://microsoft.aad.brokerplugin/{CLIENT_ID}برای WinUI با WAM.
پس از ایجاد اپ، به تب Authentication بروید و مطمئن شوید که گزینهی Allow public client flows فعال است. سپس در تب API permissions اسکوپهای موردنیاز مثل User.Read یا اسکوپهای API سفارشی خود را اضافه و در صورت لزوم Admin Consent بگیرید. Client ID و Tenant ID را از تب Overview کپی کنید. این دو مقدار قلب پیکربندی MSAL شما خواهند بود.
نصب MSAL و پیکربندی PublicClientApplication
در پروژهی MAUI خود، بستههای زیر را نصب کنید:
dotnet add package Microsoft.Identity.Client --version 4.66.2
dotnet add package Microsoft.Identity.Client.Extensions.Msal --version 4.66.2
dotnet add package Microsoft.Identity.Client.Broker --version 4.66.2حالا یک سرویس واحد برای مدیریت PublicClientApplication ایجاد کنید. این کلاس باید Singleton باشد تا Token Cache در حافظه هدر نرود:
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
public class EntraAuthService
{
private const string ClientId = "11111111-2222-3333-4444-555555555555";
private const string TenantId = "common"; // یا Tenant ID اختصاصی شما
private static readonly string[] Scopes = new[] { "User.Read" };
private readonly IPublicClientApplication _pca;
public EntraAuthService()
{
var builder = PublicClientApplicationBuilder
.Create(ClientId)
.WithAuthority($"https://login.microsoftonline.com/{TenantId}")
.WithRedirectUri(GetPlatformRedirectUri())
.WithLogging((level, message, containsPii) =>
System.Diagnostics.Debug.WriteLine($"[MSAL {level}] {message}"),
LogLevel.Info, enablePiiLogging: false);
#if WINDOWS
builder = builder.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
{
Title = "احراز هویت با حساب سازمانی"
});
#endif
_pca = builder.Build();
}
private string GetPlatformRedirectUri()
{
#if ANDROID || IOS
return $"msal{ClientId}://auth";
#elif WINDOWS
return "ms-appx-web://microsoft.aad.brokerplugin/" + ClientId;
#else
return "http://localhost";
#endif
}
}این سرویس را در MauiProgram.cs ثبت کنید تا در ViewModelها از طریق تزریق وابستگی در دسترس باشد:
builder.Services.AddSingleton<EntraAuthService>();
builder.Services.AddTransient<LoginPageViewModel>();تنظیم Redirect URI برای iOS، اندروید و ویندوز
Redirect URI حساسترین بخش پیکربندی MSAL در MAUI است و بیش از نیمی از مشکلات «چرا صفحهی لاگین باز میشود اما هرگز به اپ برنمیگردد» ریشه در این تنظیم دارد. من در اولین اپ MAUI که MSAL را برای یک بانک ادغام کردم، دقیقاً همین باگ را داشتم و باورم نمیشد که کل روزم را صرف چیزی به سادگی یک اسکیم گمشده کنم. هر پلتفرم یک روش متفاوت برای اعلام اسکیم به سیستمعامل دارد.
اندروید
در Platforms/Android/AndroidManifest.xml این activity را داخل تگ <application> اضافه کنید:
<activity
android:name="microsoft.identity.client.BrowserTabActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="msal11111111-2222-3333-4444-555555555555"
android:host="auth" />
</intent-filter>
</activity>در MainActivity.cs باید OnActivityResult را به MSAL هدایت کنید:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthenticationContinuationHelper
.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data);
}iOS
در Platforms/iOS/Info.plist اسکیم را ثبت کنید:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>msal11111111-2222-3333-4444-555555555555</string>
</array>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>msauthv2</string>
<string>msauthv3</string>
</array>در AppDelegate.cs:
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
return true;
}Broker Authentication: Microsoft Authenticator و WAM
Broker یک برنامهی سیستمی جداگانه است که فرآیند احراز هویت را از اپ شما انجام میدهد. در iOS/اندروید این نقش را Microsoft Authenticator و در ویندوز Web Account Manager (WAM) بازی میکند. مزیتهای Broker کم نیست: اعمال Conditional Access، پشتیبانی از Device Compliance در Intune، دسترسی به Windows Hello و رمزنگاری کلید در Secure Enclave بهجای ذخیرهی رفرش توکن در فایل اپ.
برای فعالسازی Broker در MSAL کافی است روش WithBroker را در Builder فراخوانی کنید:
builder = builder.WithBroker(new BrokerOptions(
BrokerOptions.OperatingSystems.Android |
BrokerOptions.OperatingSystems.Windows));در اندروید علاوه بر این، باید امضای دیجیتال اپ خود را در Entra ID ثبت کنید. برای گرفتن hash امضا از دستور زیر استفاده کنید:
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore \
| openssl sha1 -binary | openssl base64خروجی را در پرتال Entra ID، تب Authentication، بخش Android در Signature hash وارد کنید. برای build انتشار، همین کار را با keystore انتشار انجام دهید. (ما این را در پایپلاین CI/CD بهصورت خودکار میکشیم؛ دستی این کار را تکرار نکنید، چون بعد از دو سه انتشار حتماً hash انتشار را با hash دیباگ اشتباه میگیرید.)
در ویندوز، WAM بدون نیاز به نصب چیز اضافی کار میکند اما Redirect URI به فرم ms-appx-web://microsoft.aad.brokerplugin/{CLIENT_ID} میخواهد. اگر اپ MAUI شما packaged است (MSIX)، این URI بهطور خودکار توسط Windows به شما بازمیگردد. برای اپهای unpackaged باید از راهنمای رسمی WAM در Microsoft Learn پیروی کنید.
تفاوت AcquireTokenSilent و AcquireTokenInteractive
الگوی استاندارد MSAL این است که ابتدا AcquireTokenSilent فراخوانی میشود؛ اگر توکن معتبر در کش وجود داشت یا رفرش توکن هنوز اعتبار داشت، بدون تعامل کاربر برمیگردد. در غیر این صورت MsalUiRequiredException پرتاب میشود و باید به AcquireTokenInteractive برگردیم. این جداسازی به دو دلیل مهم است: کاهش تجربهی تحریککنندهی لاگینهای مکرر، و صرفهجویی در پهنای باند در شبکههای سلولی ضعیف.
public async Task<AuthenticationResult> AcquireTokenAsync()
{
var accounts = await _pca.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
try
{
return await _pca.AcquireTokenSilent(Scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// نیاز به تعامل کاربر داریم
return await _pca.AcquireTokenInteractive(Scopes)
.WithParentActivityOrWindow(GetParentWindow())
.WithUseEmbeddedWebView(false) // مرورگر سیستمی امنتر است
.ExecuteAsync();
}
}
private object GetParentWindow()
{
#if ANDROID
return Platform.CurrentActivity;
#elif IOS
return null; // MSAL از UIApplication.SharedApplication استفاده میکند
#else
return null;
#endif
}پارامتر WithUseEmbeddedWebView(false) باعث میشود از ASWebAuthenticationSession در iOS و Custom Tabs در اندروید استفاده شود که هم امنتر است (به کوکیهای SSO مرورگر دسترسی دارد) و هم برای اپهای عمومی توسط RFC 8252 (OAuth 2.0 for Native Apps) توصیهی رسمی است. اگر واقعاً به WebView داخلی نیاز دارید (مثلاً برای اپهای تحت وایتلیبل)، حواستان به قوانین اپ استور باشد؛ اپل بهطور فزایندهای WebViewهای داخلی برای OAuth را ریجکت میکند.
سریالسازی امن Token Cache در MAUI
بهطور پیشفرض، MSAL کش توکن را در حافظه نگه میدارد که با بسته شدن اپ از بین میرود. برای حفظ Login بین راهاندازیهای اپ، باید کش را سریالسازی و در استوریج امن پلتفرم ذخیره کنید. بستهی Microsoft.Identity.Client.Extensions.Msal این کار را روی iOS با Keychain، روی اندروید با AndroidX Security Crypto و روی ویندوز با DPAPI انجام میدهد.
using Microsoft.Identity.Client.Extensions.Msal;
private async Task ConfigureTokenCacheAsync()
{
var storageProperties = new StorageCreationPropertiesBuilder(
"msal_cache.bin",
FileSystem.AppDataDirectory)
.WithMacKeyChain("com.mycompany.myapp", "MSALCache")
.WithLinuxKeyring(
"com.mycompany.myapp",
MsalCacheHelper.LinuxKeyRingDefaultCollection,
"MSAL token cache",
new KeyValuePair<string, string>("Version", "1"),
new KeyValuePair<string, string>("Product", "MyApp"))
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(_pca.UserTokenCache);
}در iOS و اندروید مدیریت کش توسط MSAL خودکار انجام میشود و شما نیازی به کد اضافه ندارید. MSAL از Keychain در iOS و SharedPreferences رمزنگاریشده در اندروید استفاده میکند. اگر میخواهید حساسیت بالاتری داشته باشید و توکنهای حساس دیگری مثل توکنهای API سفارشی را هم بهطور امن نگهداری کنید، الگوی احراز هویت JWT با SecureStorage را با MSAL ترکیب کنید تا معماری منسجم داشته باشید.
Microsoft Entra External ID و کاربران خارجی
مایکروسافت در پایان ۲۰۲۳ اعلام کرد که Azure AD B2C برای مستأجران جدید در دسترس نیست و Microsoft Entra External ID جانشین رسمی آن است. خبر خوب برای توسعهدهندگان MAUI این است که همان کد MSAL بدون تغییر با External ID کار میکند. تنها URL Authority تغییر میکند:
builder = builder.WithAuthority(
"https://mytenant.ciamlogin.com/mytenant.onmicrosoft.com/");External ID برخلاف B2C بهجای Policy، از User Flow و Custom Claims Provider استفاده میکند. برای سناریوهای B2B که در آن کاربر خارجی میخواهد به منابع Entra ID شما دسترسی داشته باشد، از پارامتر WithAuthority("https://login.microsoftonline.com/organizations") استفاده کنید تا هر مستأجر Entra ID را بپذیرد. این الگو در اپهای SaaS چندمستأجرهای که در حوزهی سلامت روی آنها کار کردهام بسیار پرکاربرد است.
مدیریت خطاها و Conditional Access
MSAL چند خطای مشخص را پرتاب میکند که هر کدام نیاز به رفتار متفاوت دارد. جدول زیر پرتکرارترینها را خلاصه میکند:
| خطا | معنی | پاسخ صحیح |
|---|---|---|
MsalUiRequiredException | توکن یا کش وجود ندارد یا رفرش توکن منقضی شده | فراخوانی AcquireTokenInteractive |
MsalServiceException (invalid_grant) | کاربر رمز عبور را عوض کرده یا سیاست تغییر کرده | حذف حساب و لاگین مجدد |
MsalClientException (broker_response_not_received) | Authenticator یا WAM پاسخ نداد | Retry با تأخیر، سپس Fallback به مرورگر |
MsalUiRequiredException (interaction_required) | Conditional Access به MFA نیاز دارد | Interactive + WithClaims(claimsChallenge) |
MsalServiceException (temporarily_unavailable) | سرویس Entra ID موقتاً در دسترس نیست | Exponential backoff، حداکثر ۳ بار تلاش |
برای مدیریت Claims Challenge (که در Conditional Access یا Continuous Access Evaluation فعال میشود) باید WWW-Authenticate header بازگشتی از API را بخوانید و در فراخوانی بعدی MSAL از WithClaims استفاده کنید:
catch (MsalUiRequiredException ex) when (ex.Classification == UiRequiredExceptionClassification.ConsentRequired
|| !string.IsNullOrEmpty(ex.Claims))
{
return await _pca.AcquireTokenInteractive(Scopes)
.WithClaims(ex.Claims)
.ExecuteAsync();
}اگر اپ شما گزارش کرش خودکار دارد، دقت کنید که PII (Personally Identifiable Information) در پیامهای MSAL درج نشود. پارامتر enablePiiLogging: false باید همیشه در production فعال بماند. برای راهاندازی گزارشگیری کرش سازگار با MAUI میتوانید به مقالهی گزارشگیری Crash با Sentry پس از بازنشستگی App Center مراجعه کنید که فیلترهای مناسب برای حذف توکنها را نیز پوشش میدهد.
پرسشهای پرتکرار
تفاوت MSAL و OAuth چیست؟
OAuth 2.0 یک پروتکل استاندارد است در حالی که MSAL یک پیادهسازی مشخص از OAuth 2.0 و OpenID Connect برای پلتفرم هویت مایکروسافت است. MSAL علاوه بر جریانهای استاندارد OAuth، منطق کش توکن، رفرش خودکار، Broker و Conditional Access را کپسوله میکند که در سایر کتابخانههای عمومی OAuth وجود ندارد.
آیا MSAL روی MAUI iOS نیاز به Xcode جداگانهای دارد؟
خیر. MSAL.NET نسخه ۴.۶۶ بهطور کامل روی net8.0-ios و net10.0-ios کامپایل میشود و نیازی به bridge یا binding سفارشی ندارد. تنها نکته این است که Info.plist باید CFBundleURLTypes و LSApplicationQueriesSchemes را بهدرستی داشته باشد.
چگونه Broker را در اندروید فعال کنیم بدون اینکه کاربر Microsoft Authenticator نصب داشته باشد؟
اگر Authenticator نصب نباشد، MSAL بهطور خودکار به Custom Tabs مرورگر Fallback میکند. برای اجبار به Broker میتوانید در Entra ID، Conditional Access با Require compliant device فعال کنید که در آن صورت لاگین بدون Authenticator رد خواهد شد. اما برای اپهای عمومی این کار توصیه نمیشود.
آیا با MSAL میتوانم به Microsoft Graph دسترسی داشته باشم؟
بله. کافی است اسکوپ User.Read یا هر اسکوپ Graph دیگری را در آرایهی Scopes اضافه کنید. سپس AccessToken بازگشتی را در هدر Authorization: Bearer به https://graph.microsoft.com/v1.0/me ارسال کنید. بستهی Microsoft.Graph نیز میتواند مستقیماً از MSAL TokenCredential بگیرد.
Token Cache در MAUI چگونه بین راهاندازیها حفظ میشود؟
در iOS و اندروید MSAL بهطور خودکار از Keychain و AndroidX Security استفاده میکند و نیازی به کد اضافه نیست. در ویندوز و لینوکس باید از بستهی Microsoft.Identity.Client.Extensions.Msal و MsalCacheHelper برای سریالسازی امن استفاده کنید. رفرش توکن پیشفرض ۹۰ روز اعتبار دارد که در Conditional Access ممکن است کمتر شود.




