diff --git a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs index 94ca3f9cd2d..9956c8dcf25 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs @@ -45,6 +45,7 @@ public interface IAuthContext string Environment { get; set; } string AppName { get; set; } string Account { get; set; } + string HomeAccountId { get; set; } string CertificateThumbprint { get; set; } string CertificateSubjectName { get; set; } bool SendCertificateChain { get; set; } diff --git a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj index 54f37452ac4..9760b5f2395 100644 --- a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj +++ b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj @@ -4,7 +4,7 @@ 9.0 netstandard2.0;net6.0;net472 Microsoft.Graph.PowerShell.Authentication.Core - 2.35.1 + 2.38.0 true @@ -20,6 +20,10 @@ + + + + diff --git a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs index e4448663d72..e190172495f 100644 --- a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs +++ b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs @@ -8,6 +8,7 @@ using Microsoft.Graph.Authentication; using Microsoft.Graph.PowerShell.Authentication.Core.Extensions; using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Broker; using Microsoft.Identity.Client.Extensions.Msal; using System; using System.Diagnostics.Tracing; @@ -140,10 +141,13 @@ private static async Task GetInteractiveBrowserCre }); } await WriteAuthRecordAsync(authRecord).ConfigureAwait(false); + authContext.HomeAccountId = TryGetHomeAccountId(authRecord); return interactiveBrowserCredential; } - interactiveOptions.AuthenticationRecord = await ReadAuthRecordAsync().ConfigureAwait(false); + var interactiveAuthRecord = await ReadAuthRecordAsync().ConfigureAwait(false); + interactiveOptions.AuthenticationRecord = interactiveAuthRecord; + authContext.HomeAccountId = TryGetHomeAccountId(interactiveAuthRecord); return new InteractiveBrowserCredential(interactiveOptions); } @@ -181,10 +185,13 @@ private static async Task GetDeviceCodeCredentialAsync(IAu var deviceCodeCredential = new DeviceCodeCredential(deviceCodeOptions); var authRecord = await deviceCodeCredential.AuthenticateAsync(new TokenRequestContext(authContext.Scopes), cancellationToken).ConfigureAwait(false); await WriteAuthRecordAsync(authRecord).ConfigureAwait(false); + authContext.HomeAccountId = TryGetHomeAccountId(authRecord); return deviceCodeCredential; } - deviceCodeOptions.AuthenticationRecord = await ReadAuthRecordAsync().ConfigureAwait(false); + var deviceCodeAuthRecord = await ReadAuthRecordAsync().ConfigureAwait(false); + deviceCodeOptions.AuthenticationRecord = deviceCodeAuthRecord; + authContext.HomeAccountId = TryGetHomeAccountId(deviceCodeAuthRecord); return new DeviceCodeCredential(deviceCodeOptions); } @@ -425,17 +432,175 @@ private static bool TryFindCertificateBySubjectName(string subjectName, StoreLoc /// /// Signs out of the current session using the provided . /// - /// The to sign-out from. - public static async Task LogoutAsync() + /// + /// When true and the Windows broker (WAM) is in use, cached accounts for this module + /// are also removed from the broker. This affects the shared OS-level broker store and may + /// sign the user out of other broker-enabled applications using the same Windows account. + /// + /// The that was signed out from. + public static async Task LogoutAsync(bool signOutFromBroker = false) { var authContext = GraphSession.Instance.AuthContext; GraphSession.Instance.InMemoryTokenCache?.ClearCache(); + + // Identify the account that signed in for this session so cache clearing can be scoped to + // it. Prefer the HomeAccountId captured on the session's auth context (set at sign-in) for + // correct isolation when multiple identities share the per-user persisted store. Fall back + // to the persisted auth record, then to clearing all accounts when neither is available. + var homeAccountId = !string.IsNullOrEmpty(authContext?.HomeAccountId) + ? authContext.HomeAccountId + : await GetCurrentHomeAccountIdAsync().ConfigureAwait(false); + + if (authContext?.ContextScope == ContextScope.CurrentUser) + { + try + { + await TokenCacheUtilities.ClearPersistedTokenCacheAsync( + Constants.CacheName, + authContext.ClientId, + GetAuthorityUrl(authContext), + homeAccountId).ConfigureAwait(false); + } + catch (Exception ex) + { + // Non-fatal: persisted cache clearing may fail on some platforms. + // The auth record and in-memory state are still cleared below. + LogCacheClearFailure("Failed to clear the persisted MSAL token cache during Disconnect-MgGraph", ex); + } + } + + if (signOutFromBroker && authContext != null && ShouldUseWam(authContext)) + { + try + { + await ClearBrokerTokenCacheAsync(authContext, homeAccountId).ConfigureAwait(false); + } + catch (Exception ex) + { + // Non-fatal: removing broker accounts may fail. Disconnect still completes. + LogCacheClearFailure("Failed to remove cached accounts from the Windows broker (WAM) during Disconnect-MgGraph", ex); + } + } + GraphSession.Instance.AuthContext = null; GraphSession.Instance.GraphHttpClient = null; await DeleteAuthRecordAsync().ConfigureAwait(false); return authContext; } + /// + /// Removes cached accounts for the current module from the Windows broker (WAM). + /// This only has an effect on Windows when the broker is in use. Because the broker store is + /// shared at the OS level, removing accounts here may also sign the user out of other + /// broker-enabled applications (for example Visual Studio, Azure CLI, or Azure PowerShell) + /// that are using the same Windows account. + /// + /// + /// Removes cached accounts for the current module from the Windows broker (WAM). + /// This only has an effect on Windows when the broker is in use. When the current session's + /// account can be identified (via the persisted ), only that + /// account is removed to limit the impact on other broker-enabled applications. When no account + /// can be identified, all accounts for this module are removed as a fallback. + /// Because the broker store is shared at the OS level, removing an account here may also sign + /// the user out of other broker-enabled applications (for example Visual Studio, Azure CLI, or + /// Azure PowerShell) that are using the same Windows account. + /// + /// The whose broker accounts should be removed. + /// + /// The HomeAccountId of the current session's account, used to scope removal to that account. + /// When null or empty, all accounts for the module are removed as a fallback. + /// + private static async Task ClearBrokerTokenCacheAsync(IAuthContext authContext, string homeAccountId) + { + if (authContext is null) + throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext); + + var pca = PublicClientApplicationBuilder + .Create(authContext.ClientId) + .WithAuthority(GetAuthorityUrl(authContext)) + .WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows)) + .WithParentActivityOrWindow(WindowHandleUtlities.GetConsoleOrTerminalWindow) + .Build(); + + var accounts = await pca.GetAccountsAsync().ConfigureAwait(false); + + // Narrow removal to the account that signed in for this session, identified by the + // HomeAccountId persisted in the AuthenticationRecord. This avoids removing other accounts + // the user may have signed into via this module from the shared broker store. + if (!string.IsNullOrEmpty(homeAccountId)) + { + var matchingAccounts = accounts + .Where(a => string.Equals(a.HomeAccountId?.Identifier, homeAccountId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + if (matchingAccounts.Count > 0) + { + foreach (var account in matchingAccounts) + { + await pca.RemoveAsync(account).ConfigureAwait(false); + } + return; + } + } + + // Fallback: no identifiable session account, remove all accounts for this module. + foreach (var account in accounts) + { + await pca.RemoveAsync(account).ConfigureAwait(false); + } + } + + /// + /// Reads the HomeAccountId of the account persisted for the current session, if any. + /// Returns null when no authentication record is available or it cannot be read. + /// + private static async Task GetCurrentHomeAccountIdAsync() + { + try + { + var authRecord = await ReadAuthRecordAsync().ConfigureAwait(false); + return TryGetHomeAccountId(authRecord); + } + catch + { + // A missing or unreadable auth record simply means we cannot narrow the removal. + return null; + } + } + + /// + /// Safely reads the HomeAccountId from an . Capturing this + /// diagnostic identifier must never fail sign-in, so any error yields null. + /// + private static string TryGetHomeAccountId(AuthenticationRecord authRecord) + { + try + { + return authRecord?.HomeAccountId; + } + catch + { + return null; + } + } + + /// + /// Writes a best-effort diagnostic for a non-fatal cache clearing failure. Logging must never + /// prevent Disconnect-MgGraph from completing, so any failure to log is swallowed. + /// + private static void LogCacheClearFailure(string summary, Exception ex) + { + try + { + var writer = GraphSession.Instance.OutputWriter; + writer.WriteWarning?.Invoke($"{summary}: {ex.Message}"); + writer.WriteDebug?.Invoke($"{summary}: {ex}"); + } + catch + { + // Diagnostics are best-effort and must not break sign-out. + } + } + private static async Task ReadAuthRecordAsync() { // Try to create directory if it doesn't exist. diff --git a/src/Authentication/Authentication.Core/Utilities/TokenCacheUtilities.cs b/src/Authentication/Authentication.Core/Utilities/TokenCacheUtilities.cs new file mode 100644 index 00000000000..3af70927031 --- /dev/null +++ b/src/Authentication/Authentication.Core/Utilities/TokenCacheUtilities.cs @@ -0,0 +1,105 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Extensions.Msal; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.Graph.PowerShell.Authentication.Core.Utilities +{ + /// + /// Utilities for managing the MSAL token cache persisted to disk by Azure.Identity. + /// + internal static class TokenCacheUtilities + { + // Azure.Identity internal constants for cache storage configuration. + // See: Azure/azure-sdk-for-net - sdk/core/Azure.Core/src/Identity/Constants.cs + private const string DefaultCacheKeychainService = "Microsoft.Developer.IdentityService"; + private const string DefaultCacheKeyringSchema = "msal.cache"; + private const string DefaultCacheKeyringCollection = "default"; + private static readonly KeyValuePair DefaultCacheKeyringAttribute1 = + new KeyValuePair("MsalClientID", "Microsoft.Developer.IdentityService"); + private static readonly KeyValuePair DefaultCacheKeyringAttribute2 = + new KeyValuePair("Microsoft.Developer.IdentityService", "1.0.0.0"); + + // Azure.Identity appends CAE suffixes to the cache name internally. + private const string CaeEnabledSuffix = ".cae"; + private const string CaeDisabledSuffix = ".nocae"; + + private static readonly string DefaultCacheDirectory = + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + ".IdentityService"); + + /// + /// Clears the persisted MSAL token cache files created by Azure.Identity + /// for the given cache name. Clears both CAE-enabled and CAE-disabled variants. + /// When identifies the current session's account, only that + /// account is removed from the cache; otherwise the entire cache is cleared as a fallback. + /// + /// The cache name (e.g., "mg.msal.cache"). + /// The module's client id, used to enumerate cached accounts. + /// The authority URL used to build the public client application. + /// + /// The HomeAccountId of the current session's account. When null or empty, or when no + /// matching account is found, the entire cache is cleared. + /// + public static async Task ClearPersistedTokenCacheAsync(string cacheName, string clientId = null, string authority = null, string homeAccountId = null) + { + // Azure.Identity creates separate caches for CAE-enabled and CAE-disabled tokens. + await ClearCacheAsync(cacheName + CaeEnabledSuffix, clientId, authority, homeAccountId).ConfigureAwait(false); + await ClearCacheAsync(cacheName + CaeDisabledSuffix, clientId, authority, homeAccountId).ConfigureAwait(false); + } + + private static async Task ClearCacheAsync(string cacheFileName, string clientId, string authority, string homeAccountId) + { + var storageProperties = new StorageCreationPropertiesBuilder(cacheFileName, DefaultCacheDirectory) + .WithMacKeyChain(DefaultCacheKeychainService, cacheFileName) + .WithLinuxKeyring( + DefaultCacheKeyringSchema, + DefaultCacheKeyringCollection, + cacheFileName, + DefaultCacheKeyringAttribute1, + DefaultCacheKeyringAttribute2) + .Build(); + + var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties).ConfigureAwait(false); + + // When the current session's account can be identified, remove only that account from the + // persisted cache so other accounts the user has signed into this module remain intact. + if (!string.IsNullOrEmpty(homeAccountId) && !string.IsNullOrEmpty(clientId)) + { + var builder = PublicClientApplicationBuilder.Create(clientId); + if (!string.IsNullOrEmpty(authority)) + { + builder = builder.WithAuthority(authority); + } + var pca = builder.Build(); + cacheHelper.RegisterCache(pca.UserTokenCache); + + var accounts = await pca.GetAccountsAsync().ConfigureAwait(false); + var matchingAccounts = accounts + .Where(a => string.Equals(a.HomeAccountId?.Identifier, homeAccountId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + if (matchingAccounts.Count > 0) + { + foreach (var account in matchingAccounts) + { + await pca.RemoveAsync(account).ConfigureAwait(false); + } + return; + } + } + + // Fallback: no identifiable session account, clear the entire cache. +#pragma warning disable CS0618 // MsalCacheHelper.Clear is obsolete but is the correct approach for full cache wipe on disconnect + cacheHelper.Clear(); +#pragma warning restore CS0618 + } + } +} diff --git a/src/Authentication/Authentication.Test/TokenCache/TokenCacheUtilitiesTests.cs b/src/Authentication/Authentication.Test/TokenCache/TokenCacheUtilitiesTests.cs new file mode 100644 index 00000000000..d64b56e4c6b --- /dev/null +++ b/src/Authentication/Authentication.Test/TokenCache/TokenCacheUtilitiesTests.cs @@ -0,0 +1,146 @@ +using Microsoft.Graph.PowerShell.Authentication; +using Microsoft.Graph.PowerShell.Authentication.Core.TokenCache; +using Microsoft.Graph.PowerShell.Authentication.Core.Utilities; +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Graph.Authentication.Test.TokenCache +{ + public class TokenCacheUtilitiesTests : IDisposable + { + public TokenCacheUtilitiesTests() + { + GraphSession.Initialize(() => new GraphSession()); + GraphSession.Instance.InMemoryTokenCache = new InMemoryTokenCache(); + GraphSession.Instance.GraphOption = new GraphOption(); + } + + public void Dispose() + { + GraphSession.Reset(); + } + + [Fact] + public async Task LogoutAsyncShouldClearInMemoryCacheForProcessScope() + { + // Arrange + GraphSession.Instance.InMemoryTokenCache = new InMemoryTokenCache( + Encoding.UTF8.GetBytes("mockTokenData")); + GraphSession.Instance.AuthContext = new AuthContext + { + AuthType = AuthenticationType.UserProvidedAccessToken, + ContextScope = ContextScope.Process + }; + + // Act + var result = await AuthenticationHelpers.LogoutAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(AuthenticationType.UserProvidedAccessToken, result.AuthType); + Assert.Null(GraphSession.Instance.AuthContext); + Assert.Null(GraphSession.Instance.GraphHttpClient); + Assert.Empty(GraphSession.Instance.InMemoryTokenCache.ReadTokenData()); + } + + [Fact] + public async Task LogoutAsyncShouldNotThrowWhenAuthContextIsNull() + { + // Arrange + GraphSession.Instance.AuthContext = null; + + // Act - should not throw even though there's no auth context + var result = await AuthenticationHelpers.LogoutAsync(); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task LogoutAsyncShouldAttemptCacheClearForCurrentUserScope() + { + // Arrange + GraphSession.Instance.AuthContext = new AuthContext + { + AuthType = AuthenticationType.UserProvidedAccessToken, + ContextScope = ContextScope.CurrentUser + }; + + // Act - should not throw even if no persisted cache exists on disk + var result = await AuthenticationHelpers.LogoutAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ContextScope.CurrentUser, result.ContextScope); + Assert.Null(GraphSession.Instance.AuthContext); + } + + [Fact] + public async Task LogoutAsyncWithSignOutFromBrokerShouldNotThrowWhenBrokerNotInUse() + { + // Arrange - a custom client id with WAM disabled means the broker path is skipped, + // so no real broker accounts are touched during the test. + GraphSession.Instance.GraphOption = new GraphOption { DisableWAMForMSGraph = true }; + GraphSession.Instance.AuthContext = new AuthContext + { + ClientId = "11111111-1111-1111-1111-111111111111", + AuthType = AuthenticationType.UserProvidedAccessToken, + ContextScope = ContextScope.Process + }; + + // Act - requesting broker sign-out must not throw when the broker is not in use. + var result = await AuthenticationHelpers.LogoutAsync(signOutFromBroker: true); + + // Assert + Assert.NotNull(result); + Assert.Null(GraphSession.Instance.AuthContext); + Assert.Null(GraphSession.Instance.GraphHttpClient); + } + + [Fact] + public async Task LogoutAsyncShouldUseAuthContextHomeAccountIdForCurrentUserScope() + { + // Arrange - a CurrentUser context with a session HomeAccountId exercises the account-scoped + // file cache clearing path. With no matching account on disk it safely falls back. + GraphSession.Instance.AuthContext = new AuthContext + { + AuthType = AuthenticationType.Delegated, + ContextScope = ContextScope.CurrentUser, + HomeAccountId = "00000000-0000-0000-0000-000000000000.11111111-1111-1111-1111-111111111111" + }; + + // Act - should not throw even if no persisted cache exists on disk + var result = await AuthenticationHelpers.LogoutAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ContextScope.CurrentUser, result.ContextScope); + Assert.Equal("00000000-0000-0000-0000-000000000000.11111111-1111-1111-1111-111111111111", result.HomeAccountId); + Assert.Null(GraphSession.Instance.AuthContext); + } + + [Fact] + public async Task LogoutAsyncWithSignOutFromBrokerShouldStillClearContextForCurrentUser() + { + // Arrange - WAM disabled so the broker path is skipped; the file cache clear still runs. + GraphSession.Instance.GraphOption = new GraphOption { DisableWAMForMSGraph = true }; + GraphSession.Instance.AuthContext = new AuthContext + { + ClientId = "11111111-1111-1111-1111-111111111111", + AuthType = AuthenticationType.UserProvidedAccessToken, + ContextScope = ContextScope.CurrentUser + }; + + // Act + var result = await AuthenticationHelpers.LogoutAsync(signOutFromBroker: true); + + // Assert + Assert.NotNull(result); + Assert.Equal(ContextScope.CurrentUser, result.ContextScope); + Assert.Null(GraphSession.Instance.AuthContext); + } + } +} diff --git a/src/Authentication/Authentication/Cmdlets/DisconnectMgGraph.cs b/src/Authentication/Authentication/Cmdlets/DisconnectMgGraph.cs index b5ae307ffe2..9556ea2e2c8 100644 --- a/src/Authentication/Authentication/Cmdlets/DisconnectMgGraph.cs +++ b/src/Authentication/Authentication/Cmdlets/DisconnectMgGraph.cs @@ -17,6 +17,15 @@ public class DisconnectMgGraph : PSCmdlet { private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + /// + /// When specified, also removes cached accounts for this module from the Windows broker (WAM). + /// Because the broker store is shared at the OS level, this may sign the user out of other + /// broker-enabled applications (for example Visual Studio, Azure CLI, or Azure PowerShell) + /// that are using the same Windows account. Has no effect when the broker is not in use. + /// + [Parameter(Mandatory = false, HelpMessage = "Also removes cached accounts from the Windows broker (WAM). This is a shared, OS-level store, so it may sign you out of other broker-enabled applications (e.g. Visual Studio, Azure CLI, Azure PowerShell) using the same Windows account.")] + public SwitchParameter SignOutFromBroker { get; set; } + protected override void BeginProcessing() { base.BeginProcessing(); @@ -49,7 +58,7 @@ private async Task ProcessRecordAsync() } else { - var authContext = await AuthenticationHelpers.LogoutAsync(); + var authContext = await AuthenticationHelpers.LogoutAsync(SignOutFromBroker.IsPresent); WriteObject(authContext); } } diff --git a/src/Authentication/Authentication/Models/AuthContext.cs b/src/Authentication/Authentication/Models/AuthContext.cs index 58c4b85949e..94fc9aabef2 100644 --- a/src/Authentication/Authentication/Models/AuthContext.cs +++ b/src/Authentication/Authentication/Models/AuthContext.cs @@ -21,6 +21,7 @@ public class AuthContext : IAuthContext public string CertificateSubjectName { get; set; } public bool SendCertificateChain { get; set; } public string Account { get; set; } + public string HomeAccountId { get; set; } public string AppName { get; set; } public ContextScope ContextScope { get; set; } public X509Certificate2 Certificate { get; set; } diff --git a/src/Authentication/Authentication/test/Disconnect-MgGraph.Tests.ps1 b/src/Authentication/Authentication/test/Disconnect-MgGraph.Tests.ps1 index 2bd31cd4de6..5eb69807e8e 100644 --- a/src/Authentication/Authentication/test/Disconnect-MgGraph.Tests.ps1 +++ b/src/Authentication/Authentication/test/Disconnect-MgGraph.Tests.ps1 @@ -20,7 +20,7 @@ Describe 'Disconnect-MgGraph' { $DisconnectMgGraphCommand = Get-Command Disconnect-MgGraph $DisconnectMgGraphCommand | Should -Not -BeNullOrEmpty $DisconnectMgGraphCommand.ParameterSets | Should -HaveCount 1 - $DisconnectMgGraphCommand.ParameterSets.Parameters | Should -HaveCount 12 # PS common parameters. + $DisconnectMgGraphCommand.ParameterSets.Parameters | Should -HaveCount 13 # SignOutFromBroker + PS common parameters. } It 'Should remove current AuthContext' { diff --git a/src/Authentication/descriptions/Disconnect-MgGraph.md b/src/Authentication/descriptions/Disconnect-MgGraph.md index bf3a8afface..ce368333992 100644 --- a/src/Authentication/descriptions/Disconnect-MgGraph.md +++ b/src/Authentication/descriptions/Disconnect-MgGraph.md @@ -1 +1 @@ -Use Disconnect-MgGraph to sign out. \ No newline at end of file +Use Disconnect-MgGraph to sign out. This clears the persisted MSAL token cache from disk when using CurrentUser context scope, as well as removing the in-memory token cache and authentication record. Use the -SignOutFromBroker switch to additionally remove cached accounts from the Windows broker (WAM); note that the broker store is shared at the OS level, so this can also sign you out of other broker-enabled applications (for example Visual Studio, Azure CLI, or Azure PowerShell) that use the same Windows account. \ No newline at end of file diff --git a/src/Authentication/docs/Disconnect-MgGraph.md b/src/Authentication/docs/Disconnect-MgGraph.md index 0414a17ed25..58db78b868e 100644 --- a/src/Authentication/docs/Disconnect-MgGraph.md +++ b/src/Authentication/docs/Disconnect-MgGraph.md @@ -13,11 +13,11 @@ Once you're signed in, you'll remain signed in until you invoke Disconnect-MgGra ## SYNTAX ``` -Disconnect-MgGraph [] +Disconnect-MgGraph [-SignOutFromBroker] [-ProgressAction ] [] ``` ## DESCRIPTION -Use Disconnect-MgGraph to sign out. +Use Disconnect-MgGraph to sign out. This clears the persisted MSAL token cache from disk when using CurrentUser context scope, as well as removing the in-memory token cache and authentication record. Use the -SignOutFromBroker switch to additionally remove cached accounts from the Windows broker (WAM); note that the broker store is shared at the OS level, so this can also sign you out of other broker-enabled applications (for example Visual Studio, Azure CLI, or Azure PowerShell) that use the same Windows account. ## EXAMPLES @@ -28,8 +28,45 @@ PS C:\> Disconnect-MgGraph Use Disconnect-MgGraph to sign out. +### Example 2: Sign out and also clear the Windows broker (WAM) cache +```powershell +PS C:\> Disconnect-MgGraph -SignOutFromBroker +``` + +Signs out and additionally removes cached accounts from the Windows broker (WAM). Because the broker store is shared at the OS level, this can also sign you out of other broker-enabled applications (for example Visual Studio, Azure CLI, or Azure PowerShell) using the same Windows account. + ## PARAMETERS +### -SignOutFromBroker +Also removes cached accounts for this module from the Windows broker (WAM). Because the broker store is shared at the OS level, this can sign you out of other broker-enabled applications (for example Visual Studio, Azure CLI, or Azure PowerShell) that use the same Windows account. Has no effect when the broker is not in use. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +Determines how PowerShell responds to progress updates generated by this cmdlet. This is a common parameter introduced in PowerShell 7.4. + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/src/Authentication/examples/Disconnect-MgGraph.md b/src/Authentication/examples/Disconnect-MgGraph.md index f60fbc769a8..39cdd1ec6b6 100644 --- a/src/Authentication/examples/Disconnect-MgGraph.md +++ b/src/Authentication/examples/Disconnect-MgGraph.md @@ -3,4 +3,10 @@ ```powershell PS C:\> Disconnect-MgGraph ``` -Use Disconnect-MgGraph to sign out. \ No newline at end of file +Use Disconnect-MgGraph to sign out. + +### Example 2: Sign out and also clear the Windows broker (WAM) cache +```powershell +PS C:\> Disconnect-MgGraph -SignOutFromBroker +``` +Signs out and additionally removes cached accounts from the Windows broker (WAM). Because the broker store is shared at the OS level, this can also sign you out of other broker-enabled applications (for example Visual Studio, Azure CLI, or Azure PowerShell) using the same Windows account. \ No newline at end of file