├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── AppCreationScripts ├── AppCreationScripts.md ├── Cleanup.ps1 ├── Configure.ps1 └── sample.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── ReadmeFiles ├── ReadmeAboutTheCode.md ├── ReadmeExploreTheSample.md ├── ReadmeHowTheCodeWasCreated.md ├── ReadmeLearnMore.md ├── ReadmeNextSteps.md ├── ReadmePrerequirements.md ├── ReadmeTroubleshooting.md ├── topology.png └── topology.vsdx ├── WinUIMSALApp ├── App.xaml ├── App.xaml.cs ├── Assets │ ├── LockScreenLogo.scale-200.png │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── StoreLogo.png │ └── Wide310x150Logo.scale-200.png ├── MSAL │ ├── AzureADConfig.cs │ ├── IdentityLogger.cs │ ├── MSALClientHelper.cs │ ├── MSGraphApiConfig.cs │ └── MSGraphHelper.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Package.appxmanifest ├── Properties │ └── launchSettings.json ├── WinUIMSALApp.csproj ├── app.manifest └── appsettings.json └── ms-identity-netcore-winui.sln /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering sample apps with the Microsoft identity platform and updating configuration files using PowerShell 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. On Windows, run PowerShell as **Administrator** and navigate to the root of the cloned directory 8 | 1. In PowerShell run: 9 | 10 | ```PowerShell 11 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 12 | ``` 13 | 14 | 1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. 15 | 16 | ```PowerShell 17 | cd .\AppCreationScripts\ 18 | .\Configure.ps1 -TenantId "your test tenant's id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" 19 | ``` 20 | 21 | ### More details 22 | 23 | - [Goal of the provided scripts](#goal-of-the-provided-scripts) 24 | - [Presentation of the scripts](#presentation-of-the-scripts) 25 | - [Usage pattern for tests and DevOps scenarios](#usage-pattern-for-tests-and-DevOps-scenarios) 26 | - [How to use the app creation scripts?](#how-to-use-the-app-creation-scripts) 27 | - [Pre-requisites](#pre-requisites) 28 | - [Run the script and start running](#run-the-script-and-start-running) 29 | - [Four ways to run the script](#four-ways-to-run-the-script) 30 | - [Option 1 (interactive)](#option-1-interactive) 31 | - [Option 2 (Interactive, but create apps in a specified tenant)](#option-3-Interactive-but-create-apps-in-a-specified-tenant) 32 | - [Running the script on Azure Sovereign clouds](#running-the-script-on-Azure-Sovereign-clouds) 33 | 34 | ## Goal of the provided scripts 35 | 36 | ### Presentation of the scripts 37 | 38 | This sample comes with two PowerShell scripts, which automate the creation of the Azure Active Directory applications, and the configuration of the code for this sample. Once you run them, you will only need to build the solution and you are good to test. 39 | 40 | These scripts are: 41 | 42 | - `Configure.ps1` which: 43 | - creates Azure AD applications and their related objects (permissions, dependencies, secrets, app roles), 44 | - changes the configuration files in the sample projects. 45 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Azure AD application it created: 46 | - the identifier of the application 47 | - the AppId of the application 48 | - the url of its registration in the [Azure portal](https://portal.azure.com). 49 | 50 | - `Cleanup.ps1` which cleans-up the Azure AD objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, `git reset`). 51 | 52 | ### Usage pattern for tests and DevOps scenarios 53 | 54 | The `Configure.ps1` will stop if it tries to create an Azure AD application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below. 55 | 56 | ## How to use the app creation scripts? 57 | 58 | ### Pre-requisites 59 | 60 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 61 | 1. Navigate to the root directory of the project. 62 | 1. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command: 63 | 64 | ```PowerShell 65 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 66 | ``` 67 | 68 | ### (Optionally) install Microsoft.Graph.Applications PowerShell modules 69 | 70 | The scripts install the required PowerShell module (Microsoft.Graph.Applications) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: 71 | 72 | 1. If you have never done it already, in the PowerShell window, install the Microsoft.Graph.Applications PowerShell modules. For this: 73 | 74 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select **Run as administrator**). 75 | 2. Type: 76 | 77 | ```PowerShell 78 | Install-Module Microsoft.Graph.Applications 79 | ``` 80 | 81 | or if you cannot be administrator on your machine, run: 82 | 83 | ```PowerShell 84 | Install-Module Microsoft.Graph.Applications -Scope CurrentUser 85 | ``` 86 | 87 | ### Run the script and start running 88 | 89 | 1. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 90 | 91 | ```PowerShell 92 | cd AppCreationScripts 93 | ``` 94 | 95 | 1. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 96 | 1. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 97 | 1. select **Start** for the projects 98 | 99 | You're done! 100 | 101 | ### Two ways to run the script 102 | 103 | We advise four ways of running the script: 104 | 105 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 106 | - Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects, 107 | 108 | Here are the details on how to do this. 109 | 110 | #### Option 1 (interactive) 111 | 112 | - Just run ``.\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 113 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 114 | 115 | Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in. 116 | 117 | #### Option 2 (Interactive, but create apps in a specified tenant) 118 | 119 | if you want to create the apps in a particular tenant, you can use the following option: 120 | 121 | - Open the [Azure portal](https://portal.azure.com) 122 | - Select the Azure Active directory you are interested in (in the combo-box below your name on the top right of the browser window) 123 | - Find the "Active Directory" object in this tenant 124 | - Go to **Properties** and copy the content of the **Directory Id** property 125 | - Then use the full syntax to run the scripts: 126 | 127 | ```PowerShell 128 | $tenantId = "yourTenantIdGuid" 129 | . .\Cleanup.ps1 -TenantId $tenantId 130 | . .\Configure.ps1 -TenantId $tenantId 131 | ``` 132 | 133 | ### Running the script on Azure Sovereign clouds 134 | 135 | All the four options listed above can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`. 136 | 137 | The acceptable values for this parameter are: 138 | 139 | - AzureCloud 140 | - AzureChinaCloud 141 | - AzureUSGovernment 142 | 143 | Example: 144 | 145 | ```PowerShell 146 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureUSGovernment" 147 | . .\Configure.ps1 -AzureEnvironmentName "AzureUSGovernment" 148 | ``` 149 | -------------------------------------------------------------------------------- /AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 |  2 | [CmdletBinding()] 3 | param( 4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')] 5 | [string] $tenantId, 6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] 7 | [string] $azureEnvironmentName 8 | ) 9 | 10 | 11 | Function Cleanup 12 | { 13 | if (!$azureEnvironmentName) 14 | { 15 | $azureEnvironmentName = "Global" 16 | } 17 | 18 | <# 19 | .Description 20 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 21 | #> 22 | 23 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 24 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 25 | 26 | # Connect to the Microsoft Graph API 27 | Write-Host "Connecting to Microsoft Graph" 28 | 29 | 30 | if ($tenantId -eq "") 31 | { 32 | Connect-MgGraph -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 33 | } 34 | else 35 | { 36 | Connect-MgGraph -TenantId $tenantId -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 37 | } 38 | 39 | $context = Get-MgContext 40 | $tenantId = $context.TenantId 41 | 42 | # Get the user running the script 43 | $currentUserPrincipalName = $context.Account 44 | $user = Get-MgUser -Filter "UserPrincipalName eq '$($context.Account)'" 45 | 46 | # get the tenant we signed in to 47 | $Tenant = Get-MgOrganization 48 | $tenantName = $Tenant.DisplayName 49 | 50 | $verifiedDomain = $Tenant.VerifiedDomains | where {$_.Isdefault -eq $true} 51 | $verifiedDomainName = $verifiedDomain.Name 52 | $tenantId = $Tenant.Id 53 | 54 | Write-Host ("Connected to Tenant {0} ({1}) as account '{2}'. Domain is '{3}'" -f $Tenant.DisplayName, $Tenant.Id, $currentUserPrincipalName, $verifiedDomainName) 55 | 56 | # Removes the applications 57 | Write-Host "Cleaning-up applications from tenant '$tenantId'" 58 | 59 | Write-Host "Removing 'client' (WinUI-App-Calling-MSGraph) if needed" 60 | try 61 | { 62 | Get-MgApplication -Filter "DisplayName eq 'WinUI-App-Calling-MSGraph'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id } 63 | } 64 | catch 65 | { 66 | $message = $_ 67 | Write-Warning $Error[0] 68 | Write-Host "Unable to remove the application 'WinUI-App-Calling-MSGraph'. Error is $message. Try deleting manually." -ForegroundColor White -BackgroundColor Red 69 | } 70 | 71 | Write-Host "Making sure there are no more (WinUI-App-Calling-MSGraph) applications found, will remove if needed..." 72 | $apps = Get-MgApplication -Filter "DisplayName eq 'WinUI-App-Calling-MSGraph'" | Format-List Id, DisplayName, AppId, SignInAudience, PublisherDomain 73 | 74 | if ($apps) 75 | { 76 | Remove-MgApplication -ApplicationId $apps.Id 77 | } 78 | 79 | foreach ($app in $apps) 80 | { 81 | Remove-MgApplication -ApplicationId $app.Id 82 | Write-Host "Removed WinUI-App-Calling-MSGraph.." 83 | } 84 | 85 | # also remove service principals of this app 86 | try 87 | { 88 | Get-MgServicePrincipal -filter "DisplayName eq 'WinUI-App-Calling-MSGraph'" | ForEach-Object {Remove-MgServicePrincipal -ServicePrincipalId $_.Id -Confirm:$false} 89 | } 90 | catch 91 | { 92 | $message = $_ 93 | Write-Warning $Error[0] 94 | Write-Host "Unable to remove ServicePrincipal 'WinUI-App-Calling-MSGraph'. Error is $message. Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red 95 | } 96 | } 97 | 98 | # Pre-requisites 99 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph")) { 100 | Install-Module "Microsoft.Graph" -Scope CurrentUser 101 | } 102 | 103 | #Import-Module Microsoft.Graph 104 | 105 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Authentication")) { 106 | Install-Module "Microsoft.Graph.Authentication" -Scope CurrentUser 107 | } 108 | 109 | Import-Module Microsoft.Graph.Authentication 110 | 111 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Identity.DirectoryManagement")) { 112 | Install-Module "Microsoft.Graph.Identity.DirectoryManagement" -Scope CurrentUser 113 | } 114 | 115 | Import-Module Microsoft.Graph.Identity.DirectoryManagement 116 | 117 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { 118 | Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser 119 | } 120 | 121 | Import-Module Microsoft.Graph.Applications 122 | 123 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Groups")) { 124 | Install-Module "Microsoft.Graph.Groups" -Scope CurrentUser 125 | } 126 | 127 | Import-Module Microsoft.Graph.Groups 128 | 129 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Users")) { 130 | Install-Module "Microsoft.Graph.Users" -Scope CurrentUser 131 | } 132 | 133 | Import-Module Microsoft.Graph.Users 134 | 135 | $ErrorActionPreference = "Stop" 136 | 137 | 138 | try 139 | { 140 | Cleanup -tenantId $tenantId -environment $azureEnvironmentName 141 | } 142 | catch 143 | { 144 | $_.Exception.ToString() | out-host 145 | $message = $_ 146 | Write-Warning $Error[0] 147 | Write-Host "Unable to register apps. Error is $message." -ForegroundColor White -BackgroundColor Red 148 | } 149 | 150 | Write-Host "Disconnecting from tenant" 151 | Disconnect-MgGraph 152 | -------------------------------------------------------------------------------- /AppCreationScripts/Configure.ps1: -------------------------------------------------------------------------------- 1 | 2 | [CmdletBinding()] 3 | param( 4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')] 5 | [string] $tenantId, 6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] 7 | [string] $azureEnvironmentName 8 | ) 9 | 10 | <# 11 | This script creates the Azure AD applications needed for this sample and updates the configuration files 12 | for the visual Studio projects from the data in the Azure AD applications. 13 | 14 | In case you don't have Microsoft.Graph.Applications already installed, the script will automatically install it for the current user 15 | 16 | There are two ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. 17 | #> 18 | 19 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure 20 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is 21 | # described in $permissionType 22 | Function AddResourcePermission($requiredAccess, ` 23 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) 24 | { 25 | foreach($permission in $requiredAccesses.Trim().Split("|")) 26 | { 27 | foreach($exposedPermission in $exposedPermissions) 28 | { 29 | if ($exposedPermission.Value -eq $permission) 30 | { 31 | $resourceAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess 32 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions 33 | $resourceAccess.Id = $exposedPermission.Id # Read directory data 34 | $requiredAccess.ResourceAccess += $resourceAccess 35 | } 36 | } 37 | } 38 | } 39 | 40 | # 41 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" 42 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell 43 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) 44 | { 45 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique) 46 | if ($servicePrincipal) 47 | { 48 | $sp = $servicePrincipal 49 | } 50 | else 51 | { 52 | $sp = Get-MgServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" 53 | } 54 | $appid = $sp.AppId 55 | $requiredAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess 56 | $requiredAccess.ResourceAppId = $appid 57 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess] 58 | 59 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: 60 | if ($requiredDelegatedPermissions) 61 | { 62 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2PermissionScopes -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 63 | } 64 | 65 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application 66 | if ($requiredApplicationPermissions) 67 | { 68 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 69 | } 70 | return $requiredAccess 71 | } 72 | 73 | 74 | <#.Description 75 | This function takes a string input as a single line, matches a key value and replaces with the replacement value 76 | #> 77 | Function UpdateLine([string] $line, [string] $value) 78 | { 79 | $index = $line.IndexOf(':') 80 | $lineEnd = '' 81 | 82 | if($line[$line.Length - 1] -eq ','){ $lineEnd = ',' } 83 | 84 | if ($index -ige 0) 85 | { 86 | $line = $line.Substring(0, $index+1) + " " + '"' + $value+ '"' + $lineEnd 87 | } 88 | return $line 89 | } 90 | 91 | <#.Description 92 | This function takes a dictionary of keys to search and their replacements and replaces the placeholders in a text file 93 | #> 94 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary) 95 | { 96 | $lines = Get-Content $configFilePath 97 | $index = 0 98 | while($index -lt $lines.Length) 99 | { 100 | $line = $lines[$index] 101 | foreach($key in $dictionary.Keys) 102 | { 103 | if ($line.Contains($key)) 104 | { 105 | $lines[$index] = UpdateLine $line $dictionary[$key] 106 | } 107 | } 108 | $index++ 109 | } 110 | 111 | Set-Content -Path $configFilePath -Value $lines -Force 112 | } 113 | 114 | <#.Description 115 | This function takes a string as input and creates an instance of an Optional claim object 116 | #> 117 | Function CreateOptionalClaim([string] $name) 118 | { 119 | <#.Description 120 | This function creates a new Azure AD optional claims with default and provided values 121 | #> 122 | 123 | $appClaim = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim 124 | $appClaim.AdditionalProperties = New-Object System.Collections.Generic.List[string] 125 | $appClaim.Source = $null 126 | $appClaim.Essential = $false 127 | $appClaim.Name = $name 128 | return $appClaim 129 | } 130 | 131 | <#.Description 132 | Primary entry method to create and configure app registrations 133 | #> 134 | Function ConfigureApplications 135 | { 136 | $isOpenSSl = 'N' #temporary disable open certificate creation 137 | 138 | <#.Description 139 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the 140 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) 141 | so that they are consistent with the Applications parameters 142 | #> 143 | 144 | if (!$azureEnvironmentName) 145 | { 146 | $azureEnvironmentName = "Global" 147 | } 148 | 149 | # Connect to the Microsoft Graph API, non-interactive is not supported for the moment (Oct 2021) 150 | Write-Host "Connecting to Microsoft Graph" 151 | if ($tenantId -eq "") { 152 | Connect-MgGraph -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 153 | } 154 | else { 155 | Connect-MgGraph -TenantId $tenantId -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 156 | } 157 | 158 | $context = Get-MgContext 159 | $tenantId = $context.TenantId 160 | 161 | # Get the user running the script 162 | $currentUserPrincipalName = $context.Account 163 | $user = Get-MgUser -Filter "UserPrincipalName eq '$($context.Account)'" 164 | 165 | # get the tenant we signed in to 166 | $Tenant = Get-MgOrganization 167 | $tenantName = $Tenant.DisplayName 168 | 169 | $verifiedDomain = $Tenant.VerifiedDomains | where {$_.Isdefault -eq $true} 170 | $verifiedDomainName = $verifiedDomain.Name 171 | $tenantId = $Tenant.Id 172 | 173 | Write-Host ("Connected to Tenant {0} ({1}) as account '{2}'. Domain is '{3}'" -f $Tenant.DisplayName, $Tenant.Id, $currentUserPrincipalName, $verifiedDomainName) 174 | 175 | # Create the client AAD application 176 | Write-Host "Creating the AAD application (WinUI-App-Calling-MSGraph)" 177 | # create the application 178 | $clientAadApplication = New-MgApplication -DisplayName "WinUI-App-Calling-MSGraph" ` 179 | -PublicClient ` 180 | @{ ` 181 | } ` 182 | -SignInAudience AzureADMyOrg ` 183 | #end of command 184 | 185 | $currentAppId = $clientAadApplication.AppId 186 | $currentAppObjectId = $clientAadApplication.Id 187 | 188 | $replyUrlsForApp = "ms-appx-web://microsoft.aad.brokerplugin/$currentAppId" 189 | Update-MgApplication -ApplicationId $currentAppObjectId -PublicClient @{RedirectUris=$replyUrlsForApp} 190 | $tenantName = (Get-MgApplication -ApplicationId $currentAppObjectId).PublisherDomain 191 | #Update-MgApplication -ApplicationId $currentAppObjectId -IdentifierUris @("https://$tenantName/WinUI-App-Calling-MSGraph") 192 | 193 | # create the service principal of the newly created application 194 | $clientServicePrincipal = New-MgServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} 195 | 196 | # add the user running the script as an app owner if needed 197 | $owner = Get-MgApplicationOwner -ApplicationId $currentAppObjectId 198 | if ($owner -eq $null) 199 | { 200 | New-MgApplicationOwnerByRef -ApplicationId $currentAppObjectId -BodyParameter = @{"@odata.id" = "htps://graph.microsoft.com/v1.0/directoryObjects/$user.ObjectId"} 201 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'" 202 | } 203 | 204 | # Add Claims 205 | 206 | $optionalClaims = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaims 207 | $optionalClaims.AccessToken = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim] 208 | $optionalClaims.IdToken = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim] 209 | $optionalClaims.Saml2Token = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaim] 210 | 211 | # Add Optional Claims 212 | 213 | $newClaim = CreateOptionalClaim -name "acct" 214 | $optionalClaims.IdToken += ($newClaim) 215 | Update-MgApplication -ApplicationId $currentAppObjectId -OptionalClaims $optionalClaims 216 | Write-Host "Done creating the client application (WinUI-App-Calling-MSGraph)" 217 | 218 | # URL of the AAD application in the Azure portal 219 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$currentAppId+"/objectId/"+$currentAppObjectId+"/isMSAApp/" 220 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$currentAppId+"/objectId/"+$currentAppObjectId+"/isMSAApp/" 221 | 222 | Add-Content -Value "client$currentAppIdWinUI-App-Calling-MSGraph" -Path createdApps.html 223 | # Declare a list to hold RRA items 224 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess] 225 | 226 | # Add Required Resources Access (from 'client' to 'Microsoft Graph') 227 | Write-Host "Getting access from 'client' to 'Microsoft Graph'" 228 | $requiredPermission = GetRequiredPermissions -applicationDisplayName "Microsoft Graph"` 229 | -requiredDelegatedPermissions "User.Read" 230 | 231 | $requiredResourcesAccess.Add($requiredPermission) 232 | Write-Host "Added 'Microsoft Graph' to the RRA list." 233 | # Useful for RRA additions troubleshooting 234 | # $requiredResourcesAccess.Count 235 | # $requiredResourcesAccess 236 | 237 | Update-MgApplication -ApplicationId $currentAppObjectId -RequiredResourceAccess $requiredResourcesAccess 238 | Write-Host "Granted permissions." 239 | 240 | 241 | # print the registered app portal URL for any further navigation 242 | Write-Host "Successfully registered and configured that app registration for 'WinUI-App-Calling-MSGraph' at `n $clientPortalUrl" -ForegroundColor Green 243 | 244 | # Update config file for 'client' 245 | # $configFile = $pwd.Path + "\..\WinUIMSALApp\appsettings.json" 246 | $configFile = $(Resolve-Path ($pwd.Path + "\..\WinUIMSALApp\appsettings.json")) 247 | 248 | $dictionary = @{ "TenantId" = $tenantId;"ClientId" = $clientAadApplication.AppId }; 249 | 250 | Write-Host "Updating the sample config '$configFile' with the following config values:" -ForegroundColor Yellow 251 | $dictionary 252 | Write-Host "-----------------" 253 | 254 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary 255 | 256 | if($isOpenSSL -eq 'Y') 257 | { 258 | Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------" 259 | Write-Host "You have generated certificate using OpenSSL so follow below steps: " 260 | Write-Host "Install the certificate on your system from current folder." 261 | Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------" 262 | } 263 | Add-Content -Value "" -Path createdApps.html 264 | } # end of ConfigureApplications function 265 | 266 | # Pre-requisites 267 | 268 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph")) { 269 | Install-Module "Microsoft.Graph" -Scope CurrentUser 270 | } 271 | 272 | #Import-Module Microsoft.Graph 273 | 274 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Authentication")) { 275 | Install-Module "Microsoft.Graph.Authentication" -Scope CurrentUser 276 | } 277 | 278 | Import-Module Microsoft.Graph.Authentication 279 | 280 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Identity.DirectoryManagement")) { 281 | Install-Module "Microsoft.Graph.Identity.DirectoryManagement" -Scope CurrentUser 282 | } 283 | 284 | Import-Module Microsoft.Graph.Identity.DirectoryManagement 285 | 286 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { 287 | Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser 288 | } 289 | 290 | Import-Module Microsoft.Graph.Applications 291 | 292 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Groups")) { 293 | Install-Module "Microsoft.Graph.Groups" -Scope CurrentUser 294 | } 295 | 296 | Import-Module Microsoft.Graph.Groups 297 | 298 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Users")) { 299 | Install-Module "Microsoft.Graph.Users" -Scope CurrentUser 300 | } 301 | 302 | Import-Module Microsoft.Graph.Users 303 | 304 | Set-Content -Value "" -Path createdApps.html 305 | Add-Content -Value "" -Path createdApps.html 306 | 307 | $ErrorActionPreference = "Stop" 308 | 309 | # Run interactively (will ask you for the tenant ID) 310 | 311 | try 312 | { 313 | ConfigureApplications -tenantId $tenantId -environment $azureEnvironmentName 314 | } 315 | catch 316 | { 317 | $_.Exception.ToString() | out-host 318 | $message = $_ 319 | Write-Warning $Error[0] 320 | Write-Host "Unable to register apps. Error is $message." -ForegroundColor White -BackgroundColor Red 321 | } 322 | Write-Host "Disconnecting from tenant" 323 | Disconnect-MgGraph -------------------------------------------------------------------------------- /AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Author": "v-abeyderman", 4 | "Title": "Authenticate users with MSAL.NET in a WinUI desktop application ", 5 | "Level": 100, 6 | "Client": "WinUI Desktop app", 7 | "RepositoryUrl": "ms-identity-netcore-winui", 8 | "Endpoint": "AAD v2.0", 9 | "Description": "This sample demonstrates how to use the [Microsoft Authentication Library (MSAL) for .NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) to get an access token and call the Microsoft Graph using the MS Graph SDK from a WinUI application.", 10 | "Languages": [ "csharp" ], 11 | "Products": [ "azure-active-directory", "msal-net", "Windows", "WinUI" ], 12 | "Library": [ "MSAL.NET" ], 13 | "Platform": "WinUI" 14 | }, 15 | 16 | /* 17 | This section describes different regions of readme file 18 | */ 19 | "ReadmeScenario": { 20 | "IncludeFilePath": "", 21 | "Image": "./ReadmeFiles/topology.png", 22 | /* put additional notes, will be displayed right after image*/ 23 | "AdditionalNotesIncludeFilePath": "" 24 | }, 25 | 26 | "ReadmePrerequirements": { 27 | "FreeText": "", 28 | "IncludeFilePath": "../ReadmeFiles/ReadmePrerequirements.md" 29 | }, 30 | 31 | "ReadmeSetup": { 32 | "FreeText": "", 33 | "IncludeFilePath": "", 34 | "CreateProjectIncludeFilePath": "", 35 | "AppRegistrationIncludeFilePath": "", 36 | "RunSampleIncludeFilePath": "" 37 | }, 38 | 39 | /* It either can be a text or link to another readme file */ 40 | "ReadmeTroubleshooting": { 41 | "IncludeFilePath": "../ReadmeFiles/ReadmeTroubleshooting.md" 42 | }, 43 | 44 | /* It either can be a text or link to another readme file */ 45 | "ReadmeNextSteps": { 46 | "IncludeFilePath": "../ReadmeFiles/ReadmeNextSteps.md" 47 | }, 48 | 49 | /* It either can be a text or link to another readme file */ 50 | "ReadmeHowTheCodeWasCreated": { 51 | "IncludeFilePath": "../ReadmeFiles/ReadmeHowTheCodeWasCreated.md" 52 | }, 53 | 54 | /* It either can be a text or link to another readme file */ 55 | "ReadmeAboutTheCode": { 56 | "IncludeFilePath": "../ReadmeFiles/ReadmeAboutTheCode.md" 57 | }, 58 | 59 | /* It either can be a text or link to another readme file */ 60 | "ReadmeExploreTheSample": { 61 | "IncludeFilePath": "../ReadmeFiles/ReadmeExploreTheSample.md" 62 | }, 63 | 64 | /* It either can be a text or link to another readme file */ 65 | "ReadmeLearnMore": { 66 | "IncludeFilePath": "../ReadmeFiles/ReadmeLearnMore.md" 67 | }, 68 | 69 | /* 70 | This section describes the Azure AD Applications to configure, and their dependencies 71 | */ 72 | "AADApps": [ 73 | { 74 | "Id": "client", 75 | "Name": "WinUI-App-Calling-MSGraph", 76 | "Kind": "WinUI", 77 | "Audience": "AzureADMyOrg", 78 | "OptionalClaims": { 79 | "IdTokenClaims": [ "acct" ] 80 | }, 81 | "SampleSubPath": "WinUIMSALApp", 82 | "RequiredResourcesAccess": [ 83 | { 84 | "Resource": "Microsoft Graph", 85 | "DelegatedPermissions": [ 86 | "User.Read" 87 | ] 88 | } 89 | ] 90 | } 91 | ], 92 | 93 | /* 94 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 95 | are created in Azure AD. 96 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 97 | with respect to the root of the sample, and the mapping (which string in the config file is mapped to which value 98 | */ 99 | "CodeConfiguration": [ 100 | { 101 | "App": "client", 102 | "SettingKind": "Text", 103 | "SettingFile": "\\..\\WinUIMSALApp\\appsettings.json", 104 | "Mappings": [ 105 | { 106 | "key": "TenantId", 107 | "value": "$tenantId" 108 | }, 109 | { 110 | "key": "ClientId", 111 | "value": ".AppId" 112 | } 113 | ] 114 | } 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | name: Authenticate users with MSAL.NET in a WinUI desktop application 4 | description: This sample demonstrates how to use the [Microsoft Authentication Library (MSAL) for .NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) to get an access token and call the Microsoft Graph using the MS Graph SDK from a WinUI application. 5 | languages: 6 | - csharp 7 | products: 8 | - azure-active-directory 9 | - msal-net 10 | - Windows 11 | - WinUI 12 | urlFragment: ms-identity-netcore-winui 13 | extensions: 14 | - services: ms-identity 15 | - platform: WinUI 16 | - endpoint: AAD v2.0 17 | - level: 100 18 | - client: WinUI Desktop app 19 | - service: 20 | --- 21 | 22 | # Authenticate users with MSAL.NET in a WinUI desktop application 23 | 24 | [![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/AAD%20Samples/.NET%20client%20samples/ASP.NET%20Core%20Web%20App%20tutorial)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=XXX) 25 | 26 | * [Overview](#overview) 27 | * [Scenario](#scenario) 28 | * [Prerequisites](#prerequisites) 29 | * [Setup the sample](#setup-the-sample) 30 | * [Explore the sample](#explore-the-sample) 31 | * [Using Web Account Manager (WAM)](#using-web-account-manager-(wam)) 32 | * [Troubleshooting](#troubleshooting) 33 | * [About the code](#about-the-code) 34 | * [How the code was created](#how-the-code-was-created) 35 | * [Contributing](#contributing) 36 | * [Learn more](#learn-more) 37 | 38 | ## Overview 39 | 40 | This sample demonstrates a WinUI Desktop app that authenticates users against Azure AD. 41 | 42 | ## Scenario 43 | 44 | This sample demonstrates a WinUI Desktop app that authenticates users against Azure AD. 45 | 46 | 1. The client WinUI Desktop app uses the [MSAL.NET](https://aka.ms/msal-net) to sign-in a user and obtain a JWT [ID Token](https://aka.ms/id-tokens) from **Azure AD**. 47 | 1. The **ID Token** proves that the user has successfully authenticated against **Azure AD**. 48 | 49 | ![Scenario Image](./ReadmeFiles/topology.png) 50 | 51 | ## Prerequisites 52 | 53 | * Either [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Visual Studio Code](https://code.visualstudio.com/download) and [.NET Core SDK](https://www.microsoft.com/net/learn/get-started) 54 | * An **Azure AD** tenant. For more information, see: [How to get an Azure AD tenant](https://docs.microsoft.com/azure/active-directory/develop/test-setup-environment#get-a-test-tenant) 55 | * A user account in your **Azure AD** tenant. This sample will not work with a **personal Microsoft account**. If you're signed in to the [Azure portal](https://portal.azure.com) with a personal Microsoft account and have not created a user account in your directory before, you will need to create one before proceeding. 56 | * [Windows App SDK C# VS2022 Templates](https://learn.microsoft.com/windows/apps/windows-app-sdk/downloads) 57 | 58 | 59 | ## Setup the sample 60 | 61 | ### Step 1: Clone or download this repository 62 | 63 | From your shell or command line: 64 | 65 | ```console 66 | git clone https://github.com/Azure-Samples/ms-identity-netcore-winui.git 67 | ``` 68 | 69 | or download and extract the repository *.zip* file. 70 | 71 | > :warning: To avoid path length limitations on Windows, we recommend cloning into a directory near the root of your drive. 72 | 73 | 74 | ### Step 3: Register the sample application(s) in your tenant 75 | 76 | There is one project in this sample. To register it, you can: 77 | 78 | - follow the steps below for manually register your apps 79 | - or use PowerShell scripts that: 80 | - **automatically** creates the Azure AD applications and related objects (passwords, permissions, dependencies) for you. 81 | - modify the projects' configuration files. 82 | 83 |
84 | Expand this section if you want to use this automation: 85 | 86 | > :warning: If you have never used **Microsoft Graph PowerShell** before, we recommend you go through the [App Creation Scripts Guide](./AppCreationScripts/AppCreationScripts.md) once to ensure that your environment is prepared correctly for this step. 87 | 88 | 1. On Windows, run PowerShell as **Administrator** and navigate to the root of the cloned directory 89 | 1. In PowerShell run: 90 | 91 | ```PowerShell 92 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 93 | ``` 94 | 95 | 1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. 96 | 1. For interactive process -in PowerShell, run: 97 | 98 | ```PowerShell 99 | cd .\AppCreationScripts\ 100 | .\Configure.ps1 -TenantId "[Optional] - your tenant id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" 101 | ``` 102 | 103 | > Other ways of running the scripts are described in [App Creation Scripts guide](./AppCreationScripts/AppCreationScripts.md). The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios. 104 | 105 |
106 | 107 | #### Choose the Azure AD tenant where you want to create your applications 108 | 109 | To manually register the apps, as a first step you'll need to: 110 | 111 | 1. Sign in to the [Azure portal](https://portal.azure.com). 112 | 1. If your account is present in more than one Azure AD tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory** to change your portal session to the desired Azure AD tenant. 113 | 114 | #### Register the client app (WinUI-App-Calling-MSGraph) 115 | 116 | 1. Navigate to the [Azure portal](https://portal.azure.com) and select the **Azure Active Directory** service. 117 | 1. Select the **App Registrations** blade on the left, then select **New registration**. 118 | 1. In the **Register an application page** that appears, enter your application's registration information: 119 | 1. In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `WinUI-App-Calling-MSGraph`. 120 | 1. Under **Supported account types**, select **Accounts in this organizational directory only** 121 | 1. Select **Register** to create the application. 122 | 1. In the **Overview** blade, find and note the **Application (client) ID**. You use this value in your app's configuration file(s) later in your code. 123 | 1. In the app's registration screen, select the **Authentication** blade to the left. 124 | 1. If you don't have a platform added, select **Add a platform** and select the **Public client (mobile & desktop)** option. 125 | 1. In the **Redirect URIs** section, add **ms-appx-web://microsoft.aad.brokerplugin/{ClientId}**. 126 | The **ClientId** is the Id of the App Registration and can be found under **Overview/Application (client) ID** 127 | 1. Click **Save** to save your changes. 128 | 1. Since this app signs-in users, we will now proceed to select **delegated permissions**, which is is required by apps signing-in users. 129 | 1. In the app's registration screen, select the **API permissions** blade in the left to open the page where we add access to the APIs that your application needs: 130 | 1. Select the **Add a permission** button and then: 131 | 1. Ensure that the **Microsoft APIs** tab is selected. 132 | 1. In the *Commonly used Microsoft APIs* section, select **Microsoft Graph** 133 | * Since this app signs-in users, we will now proceed to select **delegated permissions**, which is requested by apps that signs-in users. 134 | * In the **Delegated permissions** section, select **User.Read** in the list. Use the search box if necessary. 135 | 1. Select the **Add permissions** button at the bottom. 136 | 137 | ##### Configure Optional Claims 138 | 139 | 1. Still on the same app registration, select the **Token configuration** blade to the left. 140 | 1. Select **Add optional claim**: 141 | 1. Select **optional claim type**, then choose **ID**. 142 | 1. Select the optional claim **acct**. 143 | > Provides user's account status in tenant. If the user is a **member** of the tenant, the value is *0*. If they're a **guest**, the value is *1*. 144 | 1. Select **Add** to save your changes. 145 | 146 | ##### Configure the client app (WinUI-App-Calling-MSGraph) to use your app registration 147 | 148 | Open the project in your IDE (like Visual Studio or Visual Studio Code) to configure the code. 149 | 150 | > In the steps below, "ClientID" is the same as "Application ID" or "AppId". 151 | 152 | 1. Open the `WinUIMSALApp\appsettings.json` file. 153 | 1. Find the key `TenantId` and replace the existing value with your Azure AD tenant/directory ID. 154 | 1. Find the key `ClientId` and replace the existing value with the application ID (clientId) of `WinUI-App-Calling-MSGraph` app copied from the Azure portal. 155 | 156 | ### Step 4: Running the sample 157 | 158 | Open the solution in Visual Studio and start it by pressing F5 to debug or Ctrl+F5 without debug. 159 | 160 | ## Explore the sample 161 | 162 |
163 | Expand the section 164 | 165 | Start running the sample by pressing `WinUIMSALApp (Package)` button on Visual Studio menu bar. 166 | 167 | No information is displayed because you're not logged in. 168 | 169 | Two options are provided to you , you can either use the regular sign-in or use the Windows 10 WAM broker to sign-in the user instead. 170 | 171 | Click `Sign-In and Call Microsoft Graph API` button. 172 | 173 | The UI similar to Web Browser will be displayed and give you a chance to select a user and login. You might be asked to consent to access your data on Graph API. 174 | 175 | Immediately after the UI will display basic user information as it is inside Graph API and some Token Info 176 | 177 | Click `Sign-Out` button -> all the information is deleted and `User has signed-out` message is shown. 178 | 179 | Look at Token Cache folder configured inside `appsettings.json` and find a file with binary information. 180 | 181 | If you sign-out and exit the application, then you will have to sign-in again when starting the UI. 182 | 183 | In case you were signed-in and just closed the UI, next time you will start the UI, you will be already signed-in - this is because token cache file is present and it will let you in until token expiration time is passed... 184 | The token expiration date/time is shown in `Token Info` in the UI. 185 | 186 | **So make sure to sign-out every time before closing the UI.** 187 | 188 | [Azure AD code sample survey - A .NET Core WinUI application that signs-in users and calls Microsoft Graph](https://forms.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR73pcsbpbxNJuZCMKN0lURpUN0Q5NkFVUFBDVTZTNkhSUkEzUk9aM0szQiQlQCN0PWcu) 189 | 190 | ## Using Web Account Manager (WAM) 191 | 192 | MSAL is able to call [Web Account Manager](https://learn.microsoft.com/windows/uwp/security/web-account-manager), a Windows 10 component that ships with the OS. This component acts as an authentication broker and users of your app benefit from integration with accounts known from Windows, such as the account you signed-in with in your Windows session. 193 | 194 | ### WAM value proposition 195 | 196 | Using an authentication broker such as WAM has numerous benefits. 197 | 198 | * Enhanced security (your app doesn't have to manage the powerful refresh token) 199 | * Better support for Windows Hello, Conditional Access and FIDO keys 200 | * Integration with Windows' "Email and Accounts" view 201 | * Better Single Sign-On (users don't have to reenter passwords) 202 | * Most bug fixes and enhancements will be shipped with Windows 203 | 204 | ### WAM limitations 205 | 206 | * B2C and ADFS authorities aren't supported. MSAL will fall back to a browser. 207 | * Available on Win10+ and Win Server 2019+. On Mac, Linux, and earlier versions of Windows, MSAL will fall back to a browser. 208 | * Not available on Xbox. 209 | 210 |
211 | 212 | ## Troubleshooting 213 | 214 |
215 | Expand for troubleshooting info 216 | 217 | ### "Either the user canceled the authentication or the WAM Account Picker crashed because the app is running in an elevated process" error message 218 | 219 | When an app that uses MSAL is run as an elevated process, some of these calls within WAM may fail due to different process security levels. Internally MSAL.NET uses native Windows methods ([COM](/windows/win32/com/the-component-object-model)) to integrate with WAM. Starting with version 4.32.0, MSAL will display a descriptive error message when it detects that the app process is elevated and WAM returned no accounts. 220 | 221 | One solution is to not run the app as elevated, if possible. Another solution is for the app developer to call `WindowsNativeUtils.InitializeProcessSecurity` method when the app starts up. This will set the security of the processes used by WAM to the same levels. See [this sample app](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/master/tests/devapps/WAM/NetCoreWinFormsWam/Program.cs#L18-L21) for an example. However, note, that this solution isn't guaranteed to succeed to due external factors like the underlying CLR behavior. In that case, an `MsalClientException` will be thrown. For more information, see issue [#2560](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2560). 222 | 223 | ### "WAM Account Picker did not return an account" error message 224 | 225 | This message indicates that either the application user closed the dialog that displays accounts, or the dialog itself crashed. A crash might occur if AccountsControl, a Windows control, is registered incorrectly in Windows. To resolve this issue: 226 | 227 | 1. In the taskbar, right-click **Start**, and then select **Windows PowerShell (Admin)**. 228 | 1. If you're prompted by a User Account Control (UAC) dialog, select **Yes** to start PowerShell. 229 | 1. Copy and then run the following script: 230 | 231 | ```powershell 232 | if (-not (Get-AppxPackage Microsoft.AccountsControl)) { Add-AppxPackage -Register "$env:windir\SystemApps\Microsoft.AccountsControl_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode -ForceApplicationShutdown } Get-AppxPackage Microsoft.AccountsControl 233 | 234 | > * Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. 235 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. 236 | Make sure that your questions or comments are tagged with [`azure-active-directory` `winui` `ms-identity` `adal` `msal`]. 237 | 238 | If you find a bug in the sample, raise the issue on [GitHub Issues](../../../../issues). 239 |
240 | 241 | ## About the code 242 | 243 |
244 | Expand the section 245 | 246 | For general information about how the project is organized, refer to the [tutorial](https://learn.microsoft.com/windows/apps/winui/winui3/create-your-first-winui3-app) 247 | 248 | The constructor of `MainWindow` class was modified by adding code to read configuration, and initialize MSAL and MS Graph helper classes : 249 | 250 | ```csharp 251 | 252 | var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); 253 | 254 | // Read configuration 255 | AzureADConfig azureADConfig = configuration.GetSection("AzureAD").Get(); 256 | this.MSALClientHelper = new MSALClientHelper(azureADConfig); 257 | 258 | MSGraphApiConfig graphApiConfig = configuration.GetSection("MSGraphApi").Get(); 259 | this.MSGraphHelper = new MSGraphHelper(graphApiConfig, this.MSALClientHelper); 260 | ``` 261 | 262 | The following code in *MSALClientHelper.cs* initializes the MSAL's [PublicClientApplication](https://learn.microsoft.com/azure/active-directory/develop/msal-client-applications) from the various configuration settings 263 | 264 | ```csharp 265 | private void InitializePublicClientApplicationBuilder() 266 | { 267 | this.PublicClientApplicationBuilder = PublicClientApplicationBuilder.Create(AzureADConfig.ClientId) 268 | .WithAuthority(string.Format(AzureADConfig.Authority, AzureADConfig.TenantId)) 269 | .WithRedirectUri(string.Format(AzureADConfig.RedirectURI, AzureADConfig.ClientId)) // Skipping this will make MSAL fall back to older Uri: urn:ietf:wg:oauth:2.0:oob 270 | .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 271 | .WithClientCapabilities(new string[] { "cp1" }); // declare this client app capable of receiving CAE events- https://aka.ms/clientcae 272 | } 273 | ``` 274 | 275 | Additionally the *MSALClientHelper.cs* has two methods that further prepare the MSAL's PublicClient instance with a [token cache](https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization) to sign-in using the standard authentication flow. 276 | 277 | ```csharp 278 | 279 | public async Task InitializePublicClientAppAsync() 280 | { 281 | // Initialize the MSAL library by building a public client application 282 | this.PublicClientApplication = this.PublicClientApplicationBuilder.Build(); 283 | 284 | await AttachTokenCache(); 285 | return await FetchSignedInUserFromCache().ConfigureAwait(false); 286 | } 287 | ``` 288 | 289 | or using the WAM broker. This method is called when `SignInWithBrokerButton_Click` is pressed 290 | 291 | ```csharp 292 | public async Task InitializePublicClientAppForWAMBrokerAsync(IntPtr? handle) 293 | { 294 | // Initialize the MSAL library by building a public client application for authenticating using WAM 295 | this.PublicClientApplication = this.PublicClientApplicationBuilder 296 | .WithBrokerPreview(true) 297 | .WithParentActivityOrWindow(() => { return handle.Value; }) // Specify Window handle - (required for WAM). 298 | .Build(); 299 | 300 | this.IsBrokerInitialized = true; 301 | 302 | await AttachTokenCache(); 303 | return await FetchSignedInUserFromCache().ConfigureAwait(false); 304 | } 305 | ``` 306 | 307 | finally the method `SignInTheUser()` takes care of signing-in the user and obtaining an Access Token for Microsoft Graph. if there is no tokens cached, an interactive authentication session takes place, where a user has to provide credentials and may also be asked to consent. 308 | 309 | ```csharp 310 | private async Task SignInTheUser() 311 | { 312 | try 313 | { 314 | // Trigger sign-in and token acquisition flow 315 | await MSGraphHelper.SignInAndInitializeGraphServiceClient(); 316 | 317 | DispatcherQueue.TryEnqueue(() => 318 | { 319 | ResultText.Text = "User has signed-in successfully"; 320 | TokenInfoText.Text = "Call Graph API"; 321 | 322 | SetButtonsVisibilityWhenSignedIn(); 323 | }); 324 | } 325 | catch (Exception ex) 326 | { 327 | ResultText.Text = ex.Message; 328 | } 329 | } 330 | ``` 331 | 332 | When the `CallGraphButton_Click` function is called, new `MSGraphHelper` client is used to call the various Graph API. Then a call to Graph API is done. 333 | 334 | The access token is obtained inside `SignInUserAndGetTokenUsingMSAL` method, 335 | 336 | ```csharp 337 | // Call the /me endpoint of Graph 338 | User graphUser = await this.MSGraphHelper.GetMeAsync(); 339 | 340 | // Go back to the UI thread to make changes to the UI 341 | DispatcherQueue.TryEnqueue(() => 342 | { 343 | ResultText.Text = $"Current time: {DateTime.Now.ToString("HH:mm:ss")}" + "\nDisplay Name: " + graphUser.DisplayName + "\nBusiness Phone: " + graphUser.BusinessPhones.FirstOrDefault() 344 | + "\nGiven Name: " + graphUser.GivenName + "\nid: " + graphUser.Id 345 | + "\nUser Principal Name: " + graphUser.UserPrincipalName; 346 | 347 | DisplayBasicTokenInfo(this.MSALClientHelper.AuthResult); 348 | 349 | this.SignOutButton.Visibility = Visibility.Visible; 350 | }); 351 | ``` 352 | 353 | To understand more how the buttons are linked to the callback functions, open `MainWindow.xaml` file and learn the below lines, notice Click properties: 354 | 355 | ```xml 356 |
437 | 438 | ## How the code was created 439 | 440 |
441 | 442 | Expand the section 443 | 444 | The current sample is based on [UWP sample](https://github.com/Azure-Samples/active-directory-dotnet-native-uwp-v2) in terms of UI and logical structure - it is similar for both. 445 | 446 | To build an initial project, you can use [WinUI 3 Templates for Visual Studio](https://learn.microsoft.com/windows/apps/winui/winui3/winui-project-templates-in-visual-studio) 447 | 448 | It might be helpful to read [Initial project creation instructions](https://learn.microsoft.com/windows/apps/winui/winui3/create-your-first-winui3-app) as well. 449 | 450 | ### Adding configuration 451 | 452 | To be able to use `appsettings.json` file similar to ASP.NET applications, install `Microsoft.Extensions.Configuration.Binder` and `Microsoft.Extensions.Configuration.Json`. 453 | Create new file inside your client project - `appsettings.json`, with the following contents 454 | 455 | ```json 456 | { 457 | "AzureAAD": { 458 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]", 459 | "TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]", 460 | "Authority": "https://login.microsoftonline.com/{0}", 461 | "MSGraphURL": "https://graph.microsoft.com/v1.0", 462 | "RedirectURL": "ms-appx-web://microsoft.aad.brokerplugin/{0}", 463 | "CacheFileName": "netcore_winui_cache.txt", 464 | "CacheDir": "C:/Temp", 465 | "Scopes": "user.read" //write multiple scopes separated by space, Ex: scope1 scope2 ... 466 | } 467 | } 468 | ``` 469 | 470 | The file will configure Authentication and Caching for the application. `ClientId` and `TenantId` keys will be updated either by you during manual App Registration setup or by `AppCreationScripts\Configure.ps1` if you choose automated setup. 471 | 472 | After creating the configuration file add the below lines after call to `InitializeComponent();` inside `MainWindow.xaml.cs` file, `MainWindow()` method: 473 | 474 | ```csharp 475 | var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); 476 | _winUiSettings = configuration.GetSection("AzureAAD").Get(); 477 | ``` 478 | 479 | ### Adding MSAL support and logging 480 | 481 | [MSAL.NET for public clients](https://learn.microsoft.com/azure/active-directory/develop/msal-net-initializing-client-applications) is used to Authenticate with Azure AD to gain access token to MSGraph API. 482 | Install `Microsoft.Identity.Client.Extensions.Msal` package and create a Public Client application by adding the below code immediately after configuration lines: 483 | 484 | ```csharp 485 | _PublicClientApp = PublicClientApplicationBuilder.Create(_winUiSettings.ClientId) 486 | .WithAuthority(string.Format(_winUiSettings.Authority, _winUiSettings.TenantId)) 487 | .WithRedirectUri(string.Format(_winUiSettings.RedirectURL, _winUiSettings.ClientId)) 488 | .WithLogging(new IdentityLogger(EventLogLevel.Warning), enablePiiLogging: false) 489 | .Build(); 490 | ``` 491 | 492 | Notice the `.WithLogging()` method is being called. You will have to implement `IIdentityLogger` interface in similar to how it was [implemented](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/main/WinUIMSALApp/Logging/IdentityLogger.cs) inside the current sample. 493 | 494 | Refer to the [sample source code](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/f98f3170b3759e812fdd320cead851e2c73e15d5/WinUIMSALApp/MainWindow.xaml.cs#L56) for more information about the lines you've just added. 495 | 496 | ### Adding Token Cache 497 | 498 | [Token Cache](https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache#configuring-the-token-cache) issued to enhance user experience by skipping authentication part if the user is logging-in within Access Token expiry period. Add the below code immediately after Public Client creation code: 499 | 500 | ```csharp 501 | var storageProperties = new StorageCreationPropertiesBuilder(_winUiSettings.CacheFileName, _winUiSettings.CacheDir).Build(); 502 | Task.Run(async () => await MsalCacheHelper.CreateAsync(storageProperties)).Result.RegisterCache(_PublicClientApp.UserTokenCache); 503 | ``` 504 | 505 | At the end of [MainWindow()](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/f98f3170b3759e812fdd320cead851e2c73e15d5/WinUIMSALApp/MainWindow.xaml.cs#L47) method the application tries to obtain current user account and set Sign-in button text accordingly. 506 | 507 | ### User sign-In process 508 | 509 | Before calling MSGraph API, user should authenticate. The process is started by calling `SignInAndInitializeGraphServiceClient()` method that will attempt to authenticate and create MSGraph client object. There are 2 ways to obtain the token: 510 | 511 | - `AcquireTokenSilent()` - where the application tries to obtain the access token from token cache 512 | - `AcquireTokenInteractive()` - in case of `AcquireTokenSilent()` failed with [MsalUiRequiredException](https://learn.microsoft.com/dotnet/api/microsoft.identity.client.msaluirequiredexception?view=azure-dotnet). In this case user will be offered to type their credentials into a standard authentication UI dialog box. 513 | 514 | ### Calling MSGraph API 515 | 516 | To be able to call for the [MSGraph API](https://learn.microsoft.com/graph/use-the-api), the `Microsoft.Graph` package must be installed. Then the below code inside `CallGraphButton_Click()` method is called: 517 | 518 | ```csharp 519 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(_winUiSettings.Scopes.Split(' ')); 520 | User graphUser = await graphClient.Me.Request().GetAsync(); 521 | ``` 522 | 523 | ### Additional code 524 | 525 | Take a look into [MainWindow.xaml.cs](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/main/WinUIMSALApp/MainWindow.xaml.cs) and learn how Sign-in/Sign-out buttons callback functions are being used to call MSGraph API and manage user authentication state. 526 | 527 |
528 | 529 | 530 | 531 | 532 | ## Contributing 533 | 534 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md). 535 | 536 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 537 | 538 | ## Learn more 539 | 540 | * [WinUi open source project](https://github.com/microsoft/microsoft-ui-xaml) 541 | * [ILogging interface](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/logging) 542 | * [MSGraph API](https://learn.microsoft.com/graph/use-the-api) 543 | * [Microsoft identity platform (Azure Active Directory for developers)](https://docs.microsoft.com/azure/active-directory/develop/) 544 | * [Azure AD code samples](https://docs.microsoft.com/azure/active-directory/develop/sample-v2-code) 545 | * [Overview of Microsoft Authentication Library (MSAL)](https://docs.microsoft.com/azure/active-directory/develop/msal-overview) 546 | * [Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) 547 | 548 | * To learn more about the code, visit: 549 | * [Conceptual documentation for MSAL.NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki#conceptual-documentation) and in particular: 550 | * [Acquiring tokens with authorization codes on web apps](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Acquiring-tokens-with-authorization-codes-on-web-apps) 551 | * [Customizing Token cache serialization](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization) 552 | 553 | * [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) 554 | * [Understanding Azure AD application consent experiences](https://docs.microsoft.com/azure/active-directory/develop/application-consent-experience) 555 | * [Understand user and admin consent](https://docs.microsoft.com/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant#understand-user-and-admin-consent) 556 | * [Application and service principal objects in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) 557 | * [Authentication Scenarios for Azure AD](https://docs.microsoft.com/azure/active-directory/develop/authentication-flows-app-scenarios) 558 | * [Building Zero Trust ready apps](https://aka.ms/ztdevsession) 559 | -------------------------------------------------------------------------------- /ReadmeFiles/ReadmeAboutTheCode.md: -------------------------------------------------------------------------------- 1 | ## About the code 2 | 3 |
4 | Expand the section 5 | 6 | For general information about how the project is organized, refer to the [tutorial](https://learn.microsoft.com/windows/apps/winui/winui3/create-your-first-winui3-app) 7 | 8 | The constructor of `MainWindow` class was modified by adding code to read configuration, and initialize MSAL and MS Graph helper classes : 9 | 10 | ```csharp 11 | 12 | var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); 13 | 14 | // Read configuration 15 | AzureADConfig azureADConfig = configuration.GetSection("AzureAD").Get(); 16 | this.MSALClientHelper = new MSALClientHelper(azureADConfig); 17 | 18 | MSGraphApiConfig graphApiConfig = configuration.GetSection("MSGraphApi").Get(); 19 | this.MSGraphHelper = new MSGraphHelper(graphApiConfig, this.MSALClientHelper); 20 | ``` 21 | 22 | The following code in *MSALClientHelper.cs* initializes the MSAL's [PublicClientApplication](https://learn.microsoft.com/azure/active-directory/develop/msal-client-applications) from the various configuration settings 23 | 24 | ```csharp 25 | private void InitializePublicClientApplicationBuilder() 26 | { 27 | this.PublicClientApplicationBuilder = PublicClientApplicationBuilder.Create(AzureADConfig.ClientId) 28 | .WithAuthority(string.Format(AzureADConfig.Authority, AzureADConfig.TenantId)) 29 | .WithRedirectUri(string.Format(AzureADConfig.RedirectURI, AzureADConfig.ClientId)) // Skipping this will make MSAL fall back to older Uri: urn:ietf:wg:oauth:2.0:oob 30 | .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 31 | .WithClientCapabilities(new string[] { "cp1" }); // declare this client app capable of receiving CAE events- https://aka.ms/clientcae 32 | } 33 | ``` 34 | 35 | Additionally the *MSALClientHelper.cs* has two methods that further prepare the MSAL's PublicClient instance with a [token cache](https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization) to sign-in using the standard authentication flow. 36 | 37 | ```csharp 38 | 39 | public async Task InitializePublicClientAppAsync() 40 | { 41 | // Initialize the MSAL library by building a public client application 42 | this.PublicClientApplication = this.PublicClientApplicationBuilder.Build(); 43 | 44 | await AttachTokenCache(); 45 | return await FetchSignedInUserFromCache().ConfigureAwait(false); 46 | } 47 | ``` 48 | 49 | or using the WAM broker. This method is called when `SignInWithBrokerButton_Click` is pressed 50 | 51 | ```csharp 52 | public async Task InitializePublicClientAppForWAMBrokerAsync(IntPtr? handle) 53 | { 54 | // Initialize the MSAL library by building a public client application for authenticating using WAM 55 | this.PublicClientApplication = this.PublicClientApplicationBuilder 56 | .WithBrokerPreview(true) 57 | .WithParentActivityOrWindow(() => { return handle.Value; }) // Specify Window handle - (required for WAM). 58 | .Build(); 59 | 60 | this.IsBrokerInitialized = true; 61 | 62 | await AttachTokenCache(); 63 | return await FetchSignedInUserFromCache().ConfigureAwait(false); 64 | } 65 | ``` 66 | 67 | finally the method `SignInTheUser()` takes care of signing-in the user and obtaining an Access Token for Microsoft Graph. if there is no tokens cached, an interactive authentication session takes place, where a user has to provide credentials and may also be asked to consent. 68 | 69 | ```csharp 70 | private async Task SignInTheUser() 71 | { 72 | try 73 | { 74 | // Trigger sign-in and token acquisition flow 75 | await MSGraphHelper.SignInAndInitializeGraphServiceClient(); 76 | 77 | DispatcherQueue.TryEnqueue(() => 78 | { 79 | ResultText.Text = "User has signed-in successfully"; 80 | TokenInfoText.Text = "Call Graph API"; 81 | 82 | SetButtonsVisibilityWhenSignedIn(); 83 | }); 84 | } 85 | catch (Exception ex) 86 | { 87 | ResultText.Text = ex.Message; 88 | } 89 | } 90 | ``` 91 | 92 | When the `CallGraphButton_Click` function is called, new `MSGraphHelper` client is used to call the various Graph API. Then a call to Graph API is done. 93 | 94 | The access token is obtained inside `SignInUserAndGetTokenUsingMSAL` method, 95 | 96 | ```csharp 97 | // Call the /me endpoint of Graph 98 | User graphUser = await this.MSGraphHelper.GetMeAsync(); 99 | 100 | // Go back to the UI thread to make changes to the UI 101 | DispatcherQueue.TryEnqueue(() => 102 | { 103 | ResultText.Text = $"Current time: {DateTime.Now.ToString("HH:mm:ss")}" + "\nDisplay Name: " + graphUser.DisplayName + "\nBusiness Phone: " + graphUser.BusinessPhones.FirstOrDefault() 104 | + "\nGiven Name: " + graphUser.GivenName + "\nid: " + graphUser.Id 105 | + "\nUser Principal Name: " + graphUser.UserPrincipalName; 106 | 107 | DisplayBasicTokenInfo(this.MSALClientHelper.AuthResult); 108 | 109 | this.SignOutButton.Visibility = Visibility.Visible; 110 | }); 111 | ``` 112 | 113 | To understand more how the buttons are linked to the callback functions, open `MainWindow.xaml` file and learn the below lines, notice Click properties: 114 | 115 | ```xml 116 |
197 | -------------------------------------------------------------------------------- /ReadmeFiles/ReadmeExploreTheSample.md: -------------------------------------------------------------------------------- 1 | ## Explore the sample 2 | 3 |
4 | Expand the section 5 | 6 | Start running the sample by pressing `WinUIMSALApp (Package)` button on Visual Studio menu bar. 7 | 8 | No information is displayed because you're not logged in. 9 | 10 | Two options are provided to you , you can either use the regular sign-in or use the Windows 10 WAM broker to sign-in the user instead. 11 | 12 | Click `Sign-In and Call Microsoft Graph API` button. 13 | 14 | The UI similar to Web Browser will be displayed and give you a chance to select a user and login. You might be asked to consent to access your data on Graph API. 15 | 16 | Immediately after the UI will display basic user information as it is inside Graph API and some Token Info 17 | 18 | Click `Sign-Out` button -> all the information is deleted and `User has signed-out` message is shown. 19 | 20 | Look at Token Cache folder configured inside `appsettings.json` and find a file with binary information. 21 | 22 | If you sign-out and exit the application, then you will have to sign-in again when starting the UI. 23 | 24 | In case you were signed-in and just closed the UI, next time you will start the UI, you will be already signed-in - this is because token cache file is present and it will let you in until token expiration time is passed... 25 | The token expiration date/time is shown in `Token Info` in the UI. 26 | 27 | **So make sure to sign-out every time before closing the UI.** 28 | 29 | [Azure AD code sample survey - A .NET Core WinUI application that signs-in users and calls Microsoft Graph](https://forms.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR73pcsbpbxNJuZCMKN0lURpUN0Q5NkFVUFBDVTZTNkhSUkEzUk9aM0szQiQlQCN0PWcu) 30 | 31 | ## Using Web Account Manager (WAM) 32 | 33 | MSAL is able to call [Web Account Manager](https://learn.microsoft.com/windows/uwp/security/web-account-manager), a Windows 10 component that ships with the OS. This component acts as an authentication broker and users of your app benefit from integration with accounts known from Windows, such as the account you signed-in with in your Windows session. 34 | 35 | ### WAM value proposition 36 | 37 | Using an authentication broker such as WAM has numerous benefits. 38 | 39 | * Enhanced security (your app doesn't have to manage the powerful refresh token) 40 | * Better support for Windows Hello, Conditional Access and FIDO keys 41 | * Integration with Windows' "Email and Accounts" view 42 | * Better Single Sign-On (users don't have to reenter passwords) 43 | * Most bug fixes and enhancements will be shipped with Windows 44 | 45 | ### WAM limitations 46 | 47 | * B2C and ADFS authorities aren't supported. MSAL will fall back to a browser. 48 | * Available on Win10+ and Win Server 2019+. On Mac, Linux, and earlier versions of Windows, MSAL will fall back to a browser. 49 | * Not available on Xbox. 50 | 51 |
52 | -------------------------------------------------------------------------------- /ReadmeFiles/ReadmeHowTheCodeWasCreated.md: -------------------------------------------------------------------------------- 1 | ## How the code was created 2 | 3 |
4 | 5 | Expand the section 6 | 7 | The current sample is based on [UWP sample](https://github.com/Azure-Samples/active-directory-dotnet-native-uwp-v2) in terms of UI and logical structure - it is similar for both. 8 | 9 | To build an initial project, you can use [WinUI 3 Templates for Visual Studio](https://learn.microsoft.com/windows/apps/winui/winui3/winui-project-templates-in-visual-studio) 10 | 11 | It might be helpful to read [Initial project creation instructions](https://learn.microsoft.com/windows/apps/winui/winui3/create-your-first-winui3-app) as well. 12 | 13 | ### Adding configuration 14 | 15 | To be able to use `appsettings.json` file similar to ASP.NET applications, install `Microsoft.Extensions.Configuration.Binder` and `Microsoft.Extensions.Configuration.Json`. 16 | Create new file inside your client project - `appsettings.json`, with the following contents 17 | 18 | ```json 19 | { 20 | "AzureAAD": { 21 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]", 22 | "TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]", 23 | "Authority": "https://login.microsoftonline.com/{0}", 24 | "MSGraphURL": "https://graph.microsoft.com/v1.0", 25 | "RedirectURL": "ms-appx-web://microsoft.aad.brokerplugin/{0}", 26 | "CacheFileName": "netcore_winui_cache.txt", 27 | "CacheDir": "C:/Temp", 28 | "Scopes": "user.read" //write multiple scopes separated by space, Ex: scope1 scope2 ... 29 | } 30 | } 31 | ``` 32 | 33 | The file will configure Authentication and Caching for the application. `ClientId` and `TenantId` keys will be updated either by you during manual App Registration setup or by `AppCreationScripts\Configure.ps1` if you choose automated setup. 34 | 35 | After creating the configuration file add the below lines after call to `InitializeComponent();` inside `MainWindow.xaml.cs` file, `MainWindow()` method: 36 | 37 | ```csharp 38 | var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); 39 | _winUiSettings = configuration.GetSection("AzureAAD").Get(); 40 | ``` 41 | 42 | ### Adding MSAL support and logging 43 | 44 | [MSAL.NET for public clients](https://learn.microsoft.com/azure/active-directory/develop/msal-net-initializing-client-applications) is used to Authenticate with Azure AD to gain access token to MSGraph API. 45 | Install `Microsoft.Identity.Client.Extensions.Msal` package and create a Public Client application by adding the below code immediately after configuration lines: 46 | 47 | ```csharp 48 | _PublicClientApp = PublicClientApplicationBuilder.Create(_winUiSettings.ClientId) 49 | .WithAuthority(string.Format(_winUiSettings.Authority, _winUiSettings.TenantId)) 50 | .WithRedirectUri(string.Format(_winUiSettings.RedirectURL, _winUiSettings.ClientId)) 51 | .WithLogging(new IdentityLogger(EventLogLevel.Warning), enablePiiLogging: false) 52 | .Build(); 53 | ``` 54 | 55 | Notice the `.WithLogging()` method is being called. You will have to implement `IIdentityLogger` interface in similar to how it was [implemented](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/main/WinUIMSALApp/Logging/IdentityLogger.cs) inside the current sample. 56 | 57 | Refer to the [sample source code](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/f98f3170b3759e812fdd320cead851e2c73e15d5/WinUIMSALApp/MainWindow.xaml.cs#L56) for more information about the lines you've just added. 58 | 59 | ### Adding Token Cache 60 | 61 | [Token Cache](https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache#configuring-the-token-cache) issued to enhance user experience by skipping authentication part if the user is logging-in within Access Token expiry period. Add the below code immediately after Public Client creation code: 62 | 63 | ```csharp 64 | var storageProperties = new StorageCreationPropertiesBuilder(_winUiSettings.CacheFileName, _winUiSettings.CacheDir).Build(); 65 | Task.Run(async () => await MsalCacheHelper.CreateAsync(storageProperties)).Result.RegisterCache(_PublicClientApp.UserTokenCache); 66 | ``` 67 | 68 | At the end of [MainWindow()](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/f98f3170b3759e812fdd320cead851e2c73e15d5/WinUIMSALApp/MainWindow.xaml.cs#L47) method the application tries to obtain current user account and set Sign-in button text accordingly. 69 | 70 | ### User sign-In process 71 | 72 | Before calling MSGraph API, user should authenticate. The process is started by calling `SignInAndInitializeGraphServiceClient()` method that will attempt to authenticate and create MSGraph client object. There are 2 ways to obtain the token: 73 | 74 | - `AcquireTokenSilent()` - where the application tries to obtain the access token from token cache 75 | - `AcquireTokenInteractive()` - in case of `AcquireTokenSilent()` failed with [MsalUiRequiredException](https://learn.microsoft.com/dotnet/api/microsoft.identity.client.msaluirequiredexception?view=azure-dotnet). In this case user will be offered to type their credentials into a standard authentication UI dialog box. 76 | 77 | ### Calling MSGraph API 78 | 79 | To be able to call for the [MSGraph API](https://learn.microsoft.com/graph/use-the-api), the `Microsoft.Graph` package must be installed. Then the below code inside `CallGraphButton_Click()` method is called: 80 | 81 | ```csharp 82 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(_winUiSettings.Scopes.Split(' ')); 83 | User graphUser = await graphClient.Me.Request().GetAsync(); 84 | ``` 85 | 86 | ### Additional code 87 | 88 | Take a look into [MainWindow.xaml.cs](https://github.com/Azure-Samples/ms-identity-netcore-winui/blob/main/WinUIMSALApp/MainWindow.xaml.cs) and learn how Sign-in/Sign-out buttons callback functions are being used to call MSGraph API and manage user authentication state. 89 | 90 |
91 | -------------------------------------------------------------------------------- /ReadmeFiles/ReadmeLearnMore.md: -------------------------------------------------------------------------------- 1 | ## Learn more 2 | 3 | * [WinUi open source project](https://github.com/microsoft/microsoft-ui-xaml) 4 | * [ILogging interface](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/logging) 5 | * [MSGraph API](https://learn.microsoft.com/graph/use-the-api) 6 | * [Microsoft identity platform (Azure Active Directory for developers)](https://docs.microsoft.com/azure/active-directory/develop/) 7 | * [Azure AD code samples](https://docs.microsoft.com/azure/active-directory/develop/sample-v2-code) 8 | * [Overview of Microsoft Authentication Library (MSAL)](https://docs.microsoft.com/azure/active-directory/develop/msal-overview) 9 | * [Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) 10 | 11 | * To learn more about the code, visit: 12 | * [Conceptual documentation for MSAL.NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki#conceptual-documentation) and in particular: 13 | * [Acquiring tokens with authorization codes on web apps](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Acquiring-tokens-with-authorization-codes-on-web-apps) 14 | * [Customizing Token cache serialization](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization) 15 | 16 | * [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) 17 | * [Understanding Azure AD application consent experiences](https://docs.microsoft.com/azure/active-directory/develop/application-consent-experience) 18 | * [Understand user and admin consent](https://docs.microsoft.com/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant#understand-user-and-admin-consent) 19 | * [Application and service principal objects in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) 20 | * [Authentication Scenarios for Azure AD](https://docs.microsoft.com/azure/active-directory/develop/authentication-flows-app-scenarios) 21 | * [Building Zero Trust ready apps](https://aka.ms/ztdevsession) -------------------------------------------------------------------------------- /ReadmeFiles/ReadmeNextSteps.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/ReadmeFiles/ReadmeNextSteps.md -------------------------------------------------------------------------------- /ReadmeFiles/ReadmePrerequirements.md: -------------------------------------------------------------------------------- 1 | * Either [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Visual Studio Code](https://code.visualstudio.com/download) and [.NET Core SDK](https://www.microsoft.com/net/learn/get-started) 2 | * An **Azure AD** tenant. For more information, see: [How to get an Azure AD tenant](https://docs.microsoft.com/azure/active-directory/develop/test-setup-environment#get-a-test-tenant) 3 | * A user account in your **Azure AD** tenant. This sample will not work with a **personal Microsoft account**. If you're signed in to the [Azure portal](https://portal.azure.com) with a personal Microsoft account and have not created a user account in your directory before, you will need to create one before proceeding. 4 | * [Windows App SDK C# VS2022 Templates](https://learn.microsoft.com/windows/apps/windows-app-sdk/downloads) 5 | -------------------------------------------------------------------------------- /ReadmeFiles/ReadmeTroubleshooting.md: -------------------------------------------------------------------------------- 1 | ## Troubleshooting 2 | 3 |
4 | Expand for troubleshooting info 5 | 6 | ### "Either the user canceled the authentication or the WAM Account Picker crashed because the app is running in an elevated process" error message 7 | 8 | When an app that uses MSAL is run as an elevated process, some of these calls within WAM may fail due to different process security levels. Internally MSAL.NET uses native Windows methods ([COM](/windows/win32/com/the-component-object-model)) to integrate with WAM. Starting with version 4.32.0, MSAL will display a descriptive error message when it detects that the app process is elevated and WAM returned no accounts. 9 | 10 | One solution is to not run the app as elevated, if possible. Another solution is for the app developer to call `WindowsNativeUtils.InitializeProcessSecurity` method when the app starts up. This will set the security of the processes used by WAM to the same levels. See [this sample app](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/master/tests/devapps/WAM/NetCoreWinFormsWam/Program.cs#L18-L21) for an example. However, note, that this solution isn't guaranteed to succeed to due external factors like the underlying CLR behavior. In that case, an `MsalClientException` will be thrown. For more information, see issue [#2560](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2560). 11 | 12 | ### "WAM Account Picker did not return an account" error message 13 | 14 | This message indicates that either the application user closed the dialog that displays accounts, or the dialog itself crashed. A crash might occur if AccountsControl, a Windows control, is registered incorrectly in Windows. To resolve this issue: 15 | 16 | 1. In the taskbar, right-click **Start**, and then select **Windows PowerShell (Admin)**. 17 | 1. If you're prompted by a User Account Control (UAC) dialog, select **Yes** to start PowerShell. 18 | 1. Copy and then run the following script: 19 | 20 | ```powershell 21 | if (-not (Get-AppxPackage Microsoft.AccountsControl)) { Add-AppxPackage -Register "$env:windir\SystemApps\Microsoft.AccountsControl_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode -ForceApplicationShutdown } Get-AppxPackage Microsoft.AccountsControl 22 | 23 | > * Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. 24 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. 25 | Make sure that your questions or comments are tagged with [`azure-active-directory` `winui` `ms-identity` `adal` `msal`]. 26 | 27 | If you find a bug in the sample, raise the issue on [GitHub Issues](../../../../issues). 28 |
-------------------------------------------------------------------------------- /ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /ReadmeFiles/topology.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/ReadmeFiles/topology.vsdx -------------------------------------------------------------------------------- /WinUIMSALApp/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /WinUIMSALApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Controls.Primitives; 4 | using Microsoft.UI.Xaml.Data; 5 | using Microsoft.UI.Xaml.Input; 6 | using Microsoft.UI.Xaml.Media; 7 | using Microsoft.UI.Xaml.Navigation; 8 | using Microsoft.UI.Xaml.Shapes; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Runtime.InteropServices.WindowsRuntime; 14 | using Windows.ApplicationModel; 15 | using Windows.ApplicationModel.Activation; 16 | using Windows.Foundation; 17 | using Windows.Foundation.Collections; 18 | 19 | // To learn more about WinUI, the WinUI project structure, 20 | // and more about our project templates, see: http://aka.ms/winui-project-info. 21 | 22 | namespace WinUIMSALApp 23 | { 24 | /// 25 | /// Provides application-specific behavior to supplement the default Application class. 26 | /// 27 | public partial class App : Application 28 | { 29 | /// 30 | /// Initializes the singleton application object. This is the first line of authored code 31 | /// executed, and as such is the logical equivalent of main() or WinMain(). 32 | /// 33 | public App() 34 | { 35 | this.InitializeComponent(); 36 | } 37 | 38 | /// 39 | /// Invoked when the application is launched normally by the end user. Other entry points 40 | /// will be used such as when the application is launched to open a specific file. 41 | /// 42 | /// Details about the launch request and process. 43 | protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) 44 | { 45 | m_window = new MainWindow(); 46 | m_window.Activate(); 47 | } 48 | 49 | private Window m_window; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /WinUIMSALApp/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/WinUIMSALApp/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /WinUIMSALApp/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/WinUIMSALApp/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /WinUIMSALApp/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/WinUIMSALApp/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /WinUIMSALApp/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/WinUIMSALApp/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /WinUIMSALApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/WinUIMSALApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /WinUIMSALApp/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/WinUIMSALApp/Assets/StoreLogo.png -------------------------------------------------------------------------------- /WinUIMSALApp/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-netcore-winui/50f22209aafae04e3f3edf5c6d8e40e31cabadba/WinUIMSALApp/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /WinUIMSALApp/MSAL/AzureADConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WinUIMSALApp.MSAL 4 | { 5 | /// 6 | /// App settings, populated from appsettings.json file 7 | /// 8 | public class AzureADConfig 9 | { 10 | /// 11 | /// Gets or sets the Azure AD authority. 12 | /// 13 | /// 14 | /// The Azure AD authority URL. 15 | /// 16 | /// 17 | /// - For Work or School account in your org, use your tenant ID, or domain 18 | /// - for any Work or School accounts, use organizations 19 | /// - for any Work or School accounts, or Microsoft personal account, use common 20 | /// - for Microsoft Personal account, use consumers 21 | /// 22 | public string Authority { get; set; } 23 | 24 | /// 25 | /// Gets or sets the client Id (App Id) from the app registration in the Azure AD portal. 26 | /// 27 | /// 28 | /// The client identifier. 29 | /// 30 | public string ClientId { get; set; } 31 | 32 | /// 33 | /// Gets or sets the tenant identifier (tenant Id/directory id) of the Azure AD tenant where the app registration exists. 34 | /// 35 | /// 36 | /// The tenant identifier. 37 | /// 38 | public string TenantId { get; set; } 39 | 40 | /// 41 | /// Gets or sets the redirect URI of this app as provided in the app registration portal. 42 | /// 43 | /// 44 | /// The redirect URL. 45 | /// 46 | public string RedirectURI { get; set; } 47 | 48 | /// 49 | /// Gets or sets the file name of the token cache file. 50 | /// 51 | /// 52 | /// The name of the cache file. 53 | /// 54 | public string CacheFileName { get; set; } 55 | 56 | /// 57 | /// Gets or sets the token cache file dir. 58 | /// 59 | /// 60 | /// The token cache dir. 61 | /// 62 | public string CacheDir { get; set; } 63 | } 64 | } -------------------------------------------------------------------------------- /WinUIMSALApp/MSAL/IdentityLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IdentityModel.Abstractions; 2 | using System.Diagnostics; 3 | 4 | namespace WinUIMSALApp.MSAL 5 | { 6 | /// 7 | /// An example of how to use MSAL.NET logging 8 | /// 9 | /// 10 | public class IdentityLogger : IIdentityLogger 11 | { 12 | private EventLogLevel _minLogLevel = EventLogLevel.LogAlways; 13 | 14 | /// 15 | /// Create instance of IIdentityLogger implementer and set a logging level for this instance 16 | /// 17 | /// Default: LogAlways 18 | public IdentityLogger(EventLogLevel minLogLevel = EventLogLevel.LogAlways) 19 | { 20 | _minLogLevel = minLogLevel; 21 | } 22 | 23 | public bool IsEnabled(EventLogLevel eventLogLevel) 24 | { 25 | return eventLogLevel >= _minLogLevel; 26 | } 27 | 28 | public void Log(LogEntry entry) 29 | { 30 | Debug.WriteLine($"MSAL: EventLogLevel: {entry.EventLogLevel}, Message: {entry.Message} "); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WinUIMSALApp/MSAL/MSALClientHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using Microsoft.Identity.Client.Broker; 3 | using Microsoft.Identity.Client.Extensions.Msal; 4 | using Microsoft.IdentityModel.Abstractions; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace WinUIMSALApp.MSAL 12 | { 13 | /// 14 | /// Contains methods that initialize and use the MSAL SDK 15 | /// 16 | /// 17 | public class MSALClientHelper 18 | { 19 | /// 20 | /// As for the Tenant, you can use a name as obtained from the azure portal, e.g. kko365.onmicrosoft.com" 21 | /// 22 | public readonly AzureADConfig AzureADConfig; 23 | 24 | /// 25 | /// Gets the authentication result (if available) from MSAL's various operations. 26 | /// 27 | /// 28 | /// The authentication result. 29 | /// 30 | public AuthenticationResult AuthResult { get; private set; } 31 | 32 | /// 33 | /// Gets a value indicating whether this instance of PublicClientApp was initialized with a broker . 34 | /// 35 | /// 36 | /// true if this instance is broker initialized; otherwise, false. 37 | /// 38 | public bool IsBrokerInitialized { get; private set; } 39 | 40 | /// 41 | /// Gets the MSAL public client application instance. 42 | /// 43 | /// 44 | /// The public client application. 45 | /// 46 | public IPublicClientApplication PublicClientApplication { get; private set; } 47 | 48 | private PublicClientApplicationBuilder PublicClientApplicationBuilder; 49 | 50 | /// 51 | /// Initializes a new instance of the class. 52 | /// 53 | public MSALClientHelper(AzureADConfig azureADConfig) 54 | { 55 | this.AzureADConfig = azureADConfig; 56 | 57 | this.InitializePublicClientApplicationBuilder(); 58 | } 59 | 60 | /// 61 | /// Initializes the MSAL's PublicClientApplication builder. 62 | /// 63 | /// 64 | private void InitializePublicClientApplicationBuilder() 65 | { 66 | this.PublicClientApplicationBuilder = PublicClientApplicationBuilder.Create(AzureADConfig.ClientId) 67 | .WithAuthority(string.Format(AzureADConfig.Authority, AzureADConfig.TenantId)) 68 | .WithRedirectUri(string.Format(AzureADConfig.RedirectURI, AzureADConfig.ClientId)) // Skipping this will make MSAL fall back to older Uri: urn:ietf:wg:oauth:2.0:oob 69 | .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 70 | .WithClientCapabilities(new string[] { "cp1" }); // declare this client app capable of receiving CAE events- https://aka.ms/clientcae 71 | } 72 | 73 | /// 74 | /// Initializes the public client application of MSAL.NET with the required information to correctly authenticate the user. 75 | /// 76 | /// 77 | public async Task InitializePublicClientAppAsync() 78 | { 79 | // Initialize the MSAL library by building a public client application 80 | this.PublicClientApplication = this.PublicClientApplicationBuilder.Build(); 81 | 82 | await AttachTokenCache(); 83 | return await FetchSignedInUserFromCache().ConfigureAwait(false); 84 | } 85 | 86 | /// 87 | /// Initializes the public client application of MSAL.NET with the required information to correctly authenticate the user. 88 | /// 89 | /// An IAccount of an already signed-in user (if available) 90 | public async Task InitializePublicClientAppForWAMBrokerAsync(IntPtr? handle) 91 | { 92 | // Initialize the MSAL library by building a public client application for authenticating using WAM 93 | this.PublicClientApplication = this.PublicClientApplicationBuilder 94 | .WithBrokerPreview(true) 95 | .WithParentActivityOrWindow(() => { return handle.Value; }) // Specify Window handle - (required for WAM). 96 | .Build(); 97 | 98 | this.IsBrokerInitialized = true; 99 | 100 | await AttachTokenCache(); 101 | return await FetchSignedInUserFromCache().ConfigureAwait(false); 102 | } 103 | 104 | /// 105 | /// Attaches the token cache to the Public Client app. 106 | /// 107 | /// IAccount list of already signed-in users (if available) 108 | private async Task> AttachTokenCache() 109 | { 110 | // 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 111 | var storageProperties = new StorageCreationPropertiesBuilder(AzureADConfig.CacheFileName, AzureADConfig.CacheDir).Build(); 112 | var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties); 113 | msalcachehelper.RegisterCache(this.PublicClientApplication.UserTokenCache); 114 | 115 | // If the cache file is being reused, we'd find some already-signed-in accounts 116 | return await this.PublicClientApplication.GetAccountsAsync().ConfigureAwait(false); 117 | } 118 | 119 | /// 120 | /// Signs in the user and obtains an Access token for a provided set of scopes 121 | /// 122 | /// 123 | /// Access Token 124 | public async Task SignInUserAndAcquireAccessToken(string[] scopes) 125 | { 126 | var existingUser = await FetchSignedInUserFromCache().ConfigureAwait(false); 127 | 128 | try 129 | { 130 | // 1. Try to sign-in the previously signed-in account 131 | if (existingUser != null) 132 | { 133 | this.AuthResult = await this.PublicClientApplication.AcquireTokenSilent(scopes, existingUser) 134 | .ExecuteAsync().ConfigureAwait(false); 135 | } 136 | else 137 | { 138 | if (this.IsBrokerInitialized) 139 | { 140 | Console.WriteLine("No accounts found in the cache. Trying Window's default account."); 141 | 142 | this.AuthResult = await this.PublicClientApplication 143 | .AcquireTokenSilent(scopes, Microsoft.Identity.Client.PublicClientApplication.OperatingSystemAccount) 144 | .ExecuteAsync() 145 | .ConfigureAwait(false); 146 | } 147 | else 148 | { 149 | this.AuthResult = await SignInUserInteractivelyAsync(scopes); 150 | } 151 | } 152 | } 153 | catch (MsalUiRequiredException ex) 154 | { 155 | // A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenInteractive to acquire a token interactively 156 | Debug.WriteLine($"MsalUiRequiredException: {ex.Message}"); 157 | 158 | // Must be called from UI thread 159 | this.AuthResult = await this.PublicClientApplication.AcquireTokenInteractive(scopes) 160 | .WithLoginHint(existingUser?.Username ?? String.Empty) 161 | //.WithClaims("{\"id_token\":{\"deviceid\":{\"essential\":true}}}") // you can use WithClaims() to request additional claims, like in this case a compliant device can provide the deviceid claim in token. 162 | .ExecuteAsync() 163 | .ConfigureAwait(false); 164 | } 165 | catch (MsalException msalEx) 166 | { 167 | Debug.WriteLine($"Error Acquiring Token:{Environment.NewLine}{msalEx}"); 168 | } 169 | 170 | return this.AuthResult.AccessToken; 171 | } 172 | 173 | /// 174 | /// Signs the in user and acquire access token for a provided set of scopes. 175 | /// 176 | /// The scopes. 177 | /// 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 178 | /// 179 | public async Task SignInUserAndAcquireAccessToken(string[] scopes, string extraclaims) 180 | { 181 | var existingUser = await FetchSignedInUserFromCache().ConfigureAwait(false); 182 | 183 | try 184 | { 185 | // Send the user to Azure AD for re-authentication as a silent acquisition wont resolve any CAE scenarios 186 | this.AuthResult = await PublicClientApplication.AcquireTokenInteractive(scopes) 187 | .WithLoginHint(existingUser?.Username ?? String.Empty) 188 | .WithClaims(extraclaims) 189 | .ExecuteAsync() 190 | .ConfigureAwait(false); 191 | } 192 | catch (MsalException msalEx) 193 | { 194 | Debug.WriteLine($"Error Acquiring Token interactively:{Environment.NewLine}{msalEx}"); 195 | } 196 | 197 | return this.AuthResult.AccessToken; 198 | } 199 | 200 | /// 201 | /// 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. 202 | /// 203 | /// The scopes. 204 | /// The existing account. 205 | /// 206 | private async Task SignInUserInteractivelyAsync(string[] scopes, IAccount existingAccount = null) 207 | { 208 | // If the operating system has UI 209 | if (this.PublicClientApplication.IsUserInteractive()) 210 | { 211 | return await this.PublicClientApplication.AcquireTokenInteractive(scopes) 212 | .WithLoginHint(existingAccount?.Username ?? String.Empty) 213 | // .WithParentActivityOrWindow(WindowsHelper.GetConsoleOrTerminalWindow()) // TODO: fix it 214 | .ExecuteAsync() 215 | .ConfigureAwait(false); 216 | } 217 | 218 | // If the operating system does not have UI (e.g. SSH into Linux), you can fallback to device code, however this 219 | // flow will not satisfy the "device is managed" CA policy. 220 | return await this.PublicClientApplication.AcquireTokenWithDeviceCode(scopes, (dcr) => 221 | { 222 | Console.WriteLine(dcr.Message); 223 | return Task.CompletedTask; 224 | }).ExecuteAsync().ConfigureAwait(false); 225 | } 226 | 227 | /// 228 | /// Removes the first signed-in user's record from token cache 229 | /// 230 | public async void SignOutUser() 231 | { 232 | var existingUser = await FetchSignedInUserFromCache().ConfigureAwait(false); 233 | this.SignOutUser(existingUser); 234 | } 235 | 236 | /// 237 | /// Removes a given user's record from token cache 238 | /// 239 | /// The user. 240 | public async void SignOutUser(IAccount user) 241 | { 242 | await this.PublicClientApplication.RemoveAsync(user).ConfigureAwait(false); 243 | } 244 | 245 | /// 246 | /// Fetches the signed in user from MSAL's token cache (if available). 247 | /// 248 | /// 249 | public async Task FetchSignedInUserFromCache() 250 | { 251 | // get accounts from cache 252 | IEnumerable accounts = await this.PublicClientApplication.GetAccountsAsync().ConfigureAwait(false); 253 | 254 | // Error corner case: we should always have 0 or 1 accounts, not expecting > 1 255 | // This is just an example of how to resolve this ambiguity, which can arise if more apps share a token cache. 256 | // Note that some apps prefer to use a random account from the cache. 257 | if (accounts.Count() > 1) 258 | { 259 | foreach (var acc in accounts) 260 | { 261 | await this.PublicClientApplication.RemoveAsync(acc); 262 | } 263 | 264 | return null; 265 | } 266 | 267 | return accounts.SingleOrDefault(); 268 | } 269 | } 270 | } -------------------------------------------------------------------------------- /WinUIMSALApp/MSAL/MSGraphApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WinUIMSALApp.MSAL 8 | { 9 | public class MSGraphApiConfig 10 | { 11 | /// 12 | /// Gets or sets the MS Graph base URL. 13 | /// 14 | /// 15 | /// The Microsoft Graph base URL. 16 | /// 17 | public string MSGraphBaseUrl { get; set; } 18 | 19 | /// 20 | /// Gets or sets the scopes for MS graph call. 21 | /// 22 | /// 23 | /// The scopes as space separated string. 24 | /// 25 | public string Scopes { get; set; } 26 | 27 | /// 28 | /// Gets the scopes in a format as expected by the various MSAL SDK methods. 29 | /// 30 | /// 31 | /// The scopes as array. 32 | /// 33 | public string[] ScopesArray 34 | { 35 | get 36 | { 37 | return Scopes.Split(' '); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WinUIMSALApp/MSAL/MSGraphHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Graph; 3 | using Microsoft.Identity.Client; 4 | using System; 5 | using System.Diagnostics; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | 9 | namespace WinUIMSALApp.MSAL 10 | { 11 | /// 12 | /// Contains methods to initialize and call the various MS Graph SDK methods 13 | /// 14 | /// 15 | public class MSGraphHelper 16 | { 17 | public readonly MSGraphApiConfig MSGraphApiConfig; 18 | 19 | public MSALClientHelper MSALClient { get; } 20 | private GraphServiceClient _graphServiceClient; 21 | 22 | private string[] GraphScopes; 23 | private string MSGraphBaseUrl = "https://graph.microsoft.com/v1.0"; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The graph API configuration. 29 | /// The MSALClientHelper helper instance. 30 | /// msalClientHelper 31 | public MSGraphHelper(MSGraphApiConfig graphApiConfig, MSALClientHelper msalClientHelper) 32 | { 33 | if (msalClientHelper == null) 34 | { 35 | throw new ArgumentNullException(nameof(msalClientHelper)); 36 | } 37 | 38 | this.MSGraphApiConfig = graphApiConfig; 39 | 40 | this.MSALClient = msalClientHelper; 41 | this.GraphScopes = this.MSGraphApiConfig.ScopesArray; 42 | this.MSGraphBaseUrl = this.MSGraphApiConfig.MSGraphBaseUrl; 43 | } 44 | 45 | /// 46 | /// Calls the MS Graph /me endpoint 47 | /// 48 | /// 49 | public async Task GetMeAsync() 50 | { 51 | // Call /me Api 52 | Debug.WriteLine(ConsoleColor.Yellow, $"GET {_graphServiceClient.Me.Request().RequestUrl}"); 53 | 54 | return await CallGraphWithCAEFallback(async () => 55 | { 56 | return await _graphServiceClient.Me.Request().GetAsync(); 57 | }); 58 | } 59 | 60 | /// 61 | /// Calls a Microsoft Graph API, but wraps and handle a CAE exception, if thrown 62 | /// 63 | /// The type of the object to return from MS Graph call 64 | /// Async delegate function, returning a result of the desired Graph API method. 65 | /// 66 | /// Unknown error just occurred. Message: {ex.Message} 67 | /// 68 | private async Task CallGraphWithCAEFallback(Func> graphAPIMethod) 69 | { 70 | try 71 | { 72 | return await graphAPIMethod(); 73 | } 74 | catch (ServiceException ex) when (ex.Message.Contains("Continuous access evaluation resulted in claims challenge")) 75 | { 76 | this._graphServiceClient = await SignInAndInitializeGraphServiceClientPostCAE(ex); 77 | 78 | return await graphAPIMethod(); 79 | } 80 | } 81 | 82 | /// 83 | /// Sign in user using MSAL and obtain a token for MS Graph 84 | /// 85 | /// GraphServiceClient 86 | public async Task SignInAndInitializeGraphServiceClient() 87 | { 88 | string token = await this.MSALClient.SignInUserAndAcquireAccessToken(this.GraphScopes); 89 | return await InitializeGraphServiceClientAsync(token); 90 | } 91 | 92 | /// 93 | /// Signs the in and initialize graph service client post a CAE event exception. 94 | /// 95 | /// The Graph Service exception. Contains the header required to properly process a CAE event 96 | /// 97 | private async Task SignInAndInitializeGraphServiceClientPostCAE(ServiceException ex) 98 | { 99 | // Get challenge from response of Graph API 100 | var claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(ex.ResponseHeaders); 101 | 102 | string token = await this.MSALClient.SignInUserAndAcquireAccessToken(this.GraphScopes, claimChallenge); 103 | return await InitializeGraphServiceClientAsync(token); 104 | } 105 | 106 | /// 107 | /// Bootstraps the MS Graph SDK with the provided token and returns it for use 108 | /// 109 | /// The token. 110 | /// 111 | /// A GraphServiceClient (MS Graph SDK) instance 112 | /// 113 | private async Task InitializeGraphServiceClientAsync(string token) 114 | { 115 | this._graphServiceClient = new GraphServiceClient(this.MSGraphBaseUrl, 116 | new DelegateAuthenticationProvider(async (requestMessage) => 117 | { 118 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await Task.FromResult(token)); 119 | })); 120 | 121 | return await Task.FromResult(this._graphServiceClient); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /WinUIMSALApp/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 |
ApplicationAppIdUrl in the Azure portal