├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── dotnetcore.yml ├── .gitignore ├── 1-Calling-MSGraph ├── 1-1-AzureAD │ ├── AppCreationScripts │ │ ├── AppCreationScripts.md │ │ ├── Cleanup.ps1 │ │ ├── Configure.ps1 │ │ └── sample.json │ ├── Console-Interactive-MultiTarget │ │ ├── Console-Interactive-MultiTarget.csproj │ │ ├── Program.cs │ │ └── appsettings.json │ ├── Console-Interactive.sln │ ├── README.md │ └── ReadmeFiles │ │ └── topology.png ├── 1-2-AzureB2C │ └── Placeholder.md └── 1-3-NationalClouds │ ├── README.md │ └── ReadmeFiles │ └── topology.png ├── 2-TokenCache ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json ├── Console-TokenCache.sln ├── Console-TokenCache │ ├── CacheSettings.cs │ ├── Console-TokenCache.csproj │ ├── Program.cs │ └── appsettings.json └── README.md ├── 3-CustomWebUI ├── 3-1-CustomHTML │ ├── AppCreationScripts │ │ ├── AppCreationScripts.md │ │ ├── Cleanup.ps1 │ │ ├── Configure.ps1 │ │ └── sample.json │ ├── Console-Interactive-CustomWebUI │ │ ├── Console-Interactive-CustomWebUI.csproj │ │ ├── Program.cs │ │ └── appsettings.json │ ├── Console-Interactive.sln │ ├── README.md │ └── ReadmeFiles │ │ ├── failureMessage.png │ │ ├── successMessage.png │ │ └── topology.png └── 3-2-CustomBrowser │ ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json │ ├── Console-Interactive-Core.sln │ ├── Console-Interactive-CustomWebUI │ ├── Console-Interactive-CustomWebUI.csproj │ ├── CustomWebBrowser │ │ ├── CustomBrowserWebUi.cs │ │ └── SingleMessageTcpListener.cs │ ├── Program.cs │ └── appsettings.json │ ├── README.md │ └── ReadmeFiles │ ├── failureMessage.png │ ├── successMessage.png │ └── topology.png ├── 4-DeviceCodeFlow ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json ├── Console-DeviceCodeFlow-v2.sln ├── Console-DeviceCodeFlow-v2 │ ├── Console-DeviceCodeFlow-v2.csproj │ ├── Program.cs │ └── appsettings.json ├── Readme.md └── ReadmeFiles │ └── topology.png ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md └── SECURITY.md /.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 | 46 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.101 20 | - name: Build 1-1-AzureAD 21 | run: dotnet build --configuration Release .\1-Calling-MSGraph\1-1-AzureAD\Console-Interactive.sln 22 | - name: Build 2-TokenCache 23 | run: dotnet build --configuration Release .\2-TokenCache\Console-TokenCache.sln 24 | 25 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory 8 | 1. In PowerShell run: 9 | ```PowerShell 10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 11 | ``` 12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below) 13 | ```PowerShell 14 | cd .\AppCreationScripts\ 15 | .\Configure.ps1 16 | ``` 17 | 1. Open the Visual Studio solution and click start 18 | 19 | ### More details 20 | 21 | The following paragraphs: 22 | 23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 24 | - Explain the [pre-requisites](#pre-requisites) 25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 26 | - [Interactively](#option-1-interactive) to create the app in your home tenant 27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds) 31 | 32 | ## Goal of the scripts 33 | 34 | ### Presentation of the scripts 35 | 36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra 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. 37 | 38 | These scripts are: 39 | 40 | - `Configure.ps1` which: 41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets), 42 | - changes the configuration files in the C# and JavaScript projects. 43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created: 44 | - the identifier of the application 45 | - the AppId of the application 46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 47 | 48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra 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). 49 | 50 | ### Usage pattern for tests and DevOps scenarios 51 | 52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra 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. 53 | 54 | ## How to use the app creation scripts? 55 | 56 | ### Pre-requisites 57 | 58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 59 | 2. Navigate to the root directory of the project. 60 | 3. 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: 61 | ```PowerShell 62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 63 | ``` 64 | ### (Optionally) install AzureAD PowerShell modules 65 | The scripts install the required PowerShell module (AzureAD) 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: 66 | 67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 68 | 69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 70 | 2. Type: 71 | ```PowerShell 72 | Install-Module AzureAD 73 | ``` 74 | 75 | or if you cannot be administrator on your machine, run: 76 | ```PowerShell 77 | Install-Module AzureAD -Scope CurrentUser 78 | ``` 79 | 80 | ### Run the script and start running 81 | 82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 83 | ```PowerShell 84 | cd AppCreationScripts 85 | ``` 86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 88 | 8. select **Start** for the projects 89 | 90 | You're done. this just works! 91 | 92 | ### Four ways to run the script 93 | 94 | We advise four ways of running the script: 95 | 96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 98 | - 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, 99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. 100 | 101 | Here are the details on how to do this. 102 | 103 | #### Option 1 (interactive) 104 | 105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 107 | 108 | 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. 109 | 110 | #### Option 2 (non-interactive) 111 | 112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window 113 | 114 | ```PowerShell 115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 117 | . .\Cleanup.ps1 -Credential $mycreds 118 | . .\Configure.ps1 -Credential $mycreds 119 | ``` 120 | 121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 122 | 123 | #### Option 3 (Interactive, but create apps in a specified tenant) 124 | 125 | if you want to create the apps in a particular tenant, you can use the following option: 126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com) 127 | - Select the Microsoft Entra tenant you are interested in (in the combo-box below your name on the top right of the browser window) 128 | - Find the "Active Directory" object in this tenant 129 | - Go to **Properties** and copy the content of the **Directory Id** property 130 | - Then use the full syntax to run the scripts: 131 | 132 | ```PowerShell 133 | $tenantId = "yourTenantIdGuid" 134 | . .\Cleanup.ps1 -TenantId $tenantId 135 | . .\Configure.ps1 -TenantId $tenantId 136 | ``` 137 | 138 | #### Option 4 (non-interactive, and create apps in a specified tenant) 139 | 140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: 141 | 142 | ```PowerShell 143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 145 | $tenantId = "yourTenantIdGuid" 146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 148 | ``` 149 | 150 | ### Running the script on Azure Sovereign clouds 151 | 152 | 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`. 153 | 154 | The acceptable values for this parameter are: 155 | 156 | - AzureCloud 157 | - AzureChinaCloud 158 | - AzureUSGovernment 159 | - AzureGermanyCloud 160 | 161 | Example: 162 | 163 | ```PowerShell 164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud" 165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud" 166 | ``` 167 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 | ) 7 | 8 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) { 9 | Install-Module "AzureAD" -Scope CurrentUser 10 | } 11 | Import-Module AzureAD 12 | $ErrorActionPreference = "Stop" 13 | 14 | Function Cleanup 15 | { 16 | <# 17 | .Description 18 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 19 | #> 20 | 21 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 22 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 23 | 24 | # Login to Azure PowerShell (interactive if credentials are not already provided: 25 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 26 | if (!$Credential -and $TenantId) 27 | { 28 | $creds = Connect-AzureAD -TenantId $tenantId 29 | } 30 | else 31 | { 32 | if (!$TenantId) 33 | { 34 | $creds = Connect-AzureAD -Credential $Credential 35 | } 36 | else 37 | { 38 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential 39 | } 40 | } 41 | 42 | if (!$tenantId) 43 | { 44 | $tenantId = $creds.Tenant.Id 45 | } 46 | $tenant = Get-AzureADTenantDetail 47 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name 48 | 49 | # Removes the applications 50 | Write-Host "Cleaning-up applications from tenant '$tenantName'" 51 | 52 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed" 53 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 54 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" 55 | if ($apps) 56 | { 57 | Remove-AzureADApplication -ObjectId $apps.ObjectId 58 | } 59 | 60 | foreach ($app in $apps) 61 | { 62 | Remove-AzureADApplication -ObjectId $app.ObjectId 63 | Write-Host "Removed Console-Interactive-MultiTarget-v2.." 64 | } 65 | # also remove service principals of this app 66 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 67 | 68 | } 69 | 70 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/Configure.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 | ) 7 | 8 | <# 9 | This script creates the Azure AD applications needed for this sample and updates the configuration files 10 | for the visual Studio projects from the data in the Azure AD applications. 11 | 12 | Before running this script you need to install the AzureAD cmdlets as an administrator. 13 | For this: 14 | 1) Run Powershell as an administrator 15 | 2) in the PowerShell window, type: Install-Module AzureAD 16 | 17 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. 18 | #> 19 | 20 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure 21 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is 22 | # described in $permissionType 23 | Function AddResourcePermission($requiredAccess, ` 24 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) 25 | { 26 | foreach($permission in $requiredAccesses.Trim().Split("|")) 27 | { 28 | foreach($exposedPermission in $exposedPermissions) 29 | { 30 | if ($exposedPermission.Value -eq $permission) 31 | { 32 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 33 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions 34 | $resourceAccess.Id = $exposedPermission.Id # Read directory data 35 | $requiredAccess.ResourceAccess.Add($resourceAccess) 36 | } 37 | } 38 | } 39 | } 40 | 41 | # 42 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" 43 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell 44 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) 45 | { 46 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique) 47 | if ($servicePrincipal) 48 | { 49 | $sp = $servicePrincipal 50 | } 51 | else 52 | { 53 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" 54 | } 55 | $appid = $sp.AppId 56 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 57 | $requiredAccess.ResourceAppId = $appid 58 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 59 | 60 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: 61 | if ($requiredDelegatedPermissions) 62 | { 63 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 64 | } 65 | 66 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application 67 | if ($requiredApplicationPermissions) 68 | { 69 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 70 | } 71 | return $requiredAccess 72 | } 73 | 74 | 75 | Function UpdateLine([string] $line, [string] $value) 76 | { 77 | $index = $line.IndexOf('=') 78 | $delimiter = ';' 79 | if ($index -eq -1) 80 | { 81 | $index = $line.IndexOf(':') 82 | $delimiter = ',' 83 | } 84 | if ($index -ige 0) 85 | { 86 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter 87 | } 88 | return $line 89 | } 90 | 91 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary) 92 | { 93 | $lines = Get-Content $configFilePath 94 | $index = 0 95 | while($index -lt $lines.Length) 96 | { 97 | $line = $lines[$index] 98 | foreach($key in $dictionary.Keys) 99 | { 100 | if ($line.Contains($key)) 101 | { 102 | $lines[$index] = UpdateLine $line $dictionary[$key] 103 | } 104 | } 105 | $index++ 106 | } 107 | 108 | Set-Content -Path $configFilePath -Value $lines -Force 109 | } 110 | 111 | Set-Content -Value "" -Path createdApps.html 112 | Add-Content -Value "" -Path createdApps.html 113 | 114 | $ErrorActionPreference = "Stop" 115 | 116 | Function ConfigureApplications 117 | { 118 | <#.Description 119 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the 120 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) 121 | so that they are consistent with the Applications parameters 122 | #> 123 | $commonendpoint = "common" 124 | 125 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 126 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 127 | 128 | # Login to Azure PowerShell (interactive if credentials are not already provided: 129 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 130 | if (!$Credential -and $TenantId) 131 | { 132 | $creds = Connect-AzureAD -TenantId $tenantId 133 | } 134 | else 135 | { 136 | if (!$TenantId) 137 | { 138 | $creds = Connect-AzureAD -Credential $Credential 139 | } 140 | else 141 | { 142 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential 143 | } 144 | } 145 | 146 | if (!$tenantId) 147 | { 148 | $tenantId = $creds.Tenant.Id 149 | } 150 | 151 | $tenant = Get-AzureADTenantDetail 152 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name 153 | 154 | # Get the user running the script to add the user as the app owner 155 | $user = Get-AzureADUser -ObjectId $creds.Account.Id 156 | 157 | # Create the client AAD application 158 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)" 159 | # create the application 160 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" ` 161 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" ` 162 | -AvailableToOtherTenants $True ` 163 | -PublicClient $True 164 | 165 | # create the service principal of the newly created application 166 | $currentAppId = $clientAadApplication.AppId 167 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} 168 | 169 | # add the user running the script as an app owner if needed 170 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId 171 | if ($owner -eq $null) 172 | { 173 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId 174 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'" 175 | } 176 | 177 | 178 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)" 179 | 180 | # URL of the AAD application in the Azure portal 181 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 182 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 183 | Add-Content -Value "" -Path createdApps.html 184 | 185 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 186 | 187 | # Add Required Resources Access (from 'client' to 'Microsoft Graph') 188 | Write-Host "Getting access from 'client' to 'Microsoft Graph'" 189 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` 190 | -requiredDelegatedPermissions "User.Read" ` 191 | 192 | $requiredResourcesAccess.Add($requiredPermissions) 193 | 194 | 195 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess 196 | Write-Host "Granted permissions." 197 | 198 | # Update config file for 'client' 199 | $configFile = $pwd.Path + "\..\Console-Interactive-MultiTarget\appsettings.json" 200 | Write-Host "Updating the sample code ($configFile)" 201 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId }; 202 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary 203 | 204 | Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
client$currentAppIdConsole-Interactive-MultiTarget-v2
" -Path createdApps.html 205 | } 206 | 207 | # Pre-requisites 208 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { 209 | Install-Module "AzureAD" -Scope CurrentUser 210 | } 211 | 212 | Import-Module AzureAD 213 | 214 | # Run interactively (will ask you for the tenant ID) 215 | ConfigureApplications -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API from a multi-target console application.", 4 | "Level": 100, 5 | "Client": ".NET Desktop (Console)", 6 | "Service": "Microsoft Graph", 7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial", 8 | "Endpoint": "AAD v2.0" 9 | }, 10 | 11 | /* 12 | This section describes the Azure AD Applications to configure, and their dependencies 13 | */ 14 | "AADApps": [ 15 | { 16 | "Id": "client", 17 | "Name": "Console-Interactive-MultiTarget-v2", 18 | "Kind": "Desktop", 19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost", 20 | "RequiredResourcesAccess": [ 21 | { 22 | "Resource": "Microsoft Graph", 23 | "DelegatedPermissions": [ "User.Read" ] 24 | } 25 | ] 26 | } 27 | ], 28 | 29 | /* 30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 31 | are created in Azure AD. 32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 34 | */ 35 | "CodeConfiguration": [ 36 | { 37 | "App": "client", 38 | "SettingKind": "JSon", 39 | "SettingFile": "\\..\\Console-Interactive-MultiTarget\\appsettings.json", 40 | "Mappings": [ 41 | { 42 | "key": "ClientId", 43 | "value": ".AppId" 44 | }, 45 | { 46 | "key": "TenantId", 47 | "value": "$tenantId" 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/Console-Interactive-MultiTarget/Console-Interactive-MultiTarget.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1; net472 6 | Console_Interactive_MultiTarget 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/Console-Interactive-MultiTarget/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Graph; 3 | using Microsoft.Identity.Client; 4 | using System; 5 | using System.Linq; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | 9 | namespace Console_Interactive_MultiTarget 10 | { 11 | internal class Program 12 | { 13 | private static PublicClientApplicationOptions appConfiguration = null; 14 | private static IConfiguration configuration; 15 | private static string MSGraphURL; 16 | 17 | // The MSAL Public client app 18 | private static IPublicClientApplication application; 19 | 20 | private static async Task Main(string[] args) 21 | { 22 | // Using appsettings.json for our configuration settings 23 | var builder = new ConfigurationBuilder() 24 | .SetBasePath(System.IO.Directory.GetCurrentDirectory()) 25 | .AddJsonFile("appsettings.json"); 26 | 27 | configuration = builder.Build(); 28 | 29 | appConfiguration = configuration 30 | .Get(); 31 | 32 | // We intend to obtain a token for Graph for the following scopes (permissions) 33 | string[] scopes = new[] { "user.read" }; 34 | 35 | MSGraphURL = configuration.GetValue("GraphApiUrl"); 36 | 37 | // Sign-in user using MSAL and obtain an access token for MS Graph 38 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes); 39 | 40 | // Call the /me endpoint of MS Graph 41 | await CallMSGraph(graphClient); 42 | } 43 | 44 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes) 45 | { 46 | string authority = string.Concat(configuration.Instance, configuration.TenantId); 47 | 48 | // Initialize the MSAL library by building a public client application 49 | application = PublicClientApplicationBuilder.Create(configuration.ClientId) 50 | .WithAuthority(authority) 51 | .WithDefaultRedirectUri() 52 | .Build(); 53 | 54 | 55 | AuthenticationResult result; 56 | try 57 | { 58 | var accounts = await application.GetAccountsAsync(); 59 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) 60 | .ExecuteAsync(); 61 | } 62 | catch (MsalUiRequiredException ex) 63 | { 64 | result = await application.AcquireTokenInteractive(scopes) 65 | .WithClaims(ex.Claims) 66 | .ExecuteAsync(); 67 | } 68 | 69 | return result.AccessToken; 70 | } 71 | 72 | /// 73 | /// Sign in user using MSAL and obtain a token for MS Graph 74 | /// 75 | /// 76 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes) 77 | { 78 | GraphServiceClient graphClient = new GraphServiceClient(MSGraphURL, 79 | new DelegateAuthenticationProvider(async (requestMessage) => 80 | { 81 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes)); 82 | })); 83 | 84 | return await Task.FromResult(graphClient); 85 | } 86 | 87 | /// 88 | /// Call MS Graph and print results 89 | /// 90 | /// 91 | /// 92 | private static async Task CallMSGraph(GraphServiceClient graphClient) 93 | { 94 | var me = await graphClient.Me.Request().GetAsync(); 95 | 96 | // Printing the results 97 | Console.WriteLine("-------- Data from call to MS Graph --------"); 98 | Console.Write(Environment.NewLine); 99 | Console.WriteLine($"Id: {me.Id}"); 100 | Console.WriteLine($"Display Name: {me.DisplayName}"); 101 | Console.WriteLine($"Email: {me.Mail}"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/Console-Interactive-MultiTarget/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Instance": "https://login.microsoftonline.com/", 3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]", 4 | "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]", 5 | "RedirectUri": "http://localhost", 6 | "GraphApiUrl": "https://graph.microsoft.com/v1.0/" 7 | } 8 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/Console-Interactive.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console-Interactive-MultiTarget", "Console-Interactive-MultiTarget\Console-Interactive-MultiTarget.csproj", "{D8504453-21E5-4381-9485-454EBC88C734}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D8504453-21E5-4381-9485-454EBC88C734}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {D8504453-21E5-4381-9485-454EBC88C734}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {D8504453-21E5-4381-9485-454EBC88C734}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {D8504453-21E5-4381-9485-454EBC88C734}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {42EE218E-9522-4DBA-9DFE-2EC0FB0CD3D5} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-1-AzureAD/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/1-Calling-MSGraph/1-1-AzureAD/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-2-AzureB2C/Placeholder.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/1-Calling-MSGraph/1-2-AzureB2C/Placeholder.md -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-3-NationalClouds/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | services: active-directory 3 | platforms: dotnet 4 | author: TiagoBrenck 5 | level: 100 6 | client: .NET Desktop (Console) 7 | service: Microsoft Graph 8 | endpoint: Microsoft identity platform 9 | page_type: sample 10 | languages: 11 | - csharp 12 | products: 13 | - azure 14 | - microsoft-entra-id 15 | - dotnet 16 | - office-ms-graph 17 | description: "This sample demonstrates a .NET Desktop (Console) application calling Microsoft Graph on National clouds" 18 | --- 19 | 20 | # Using the Microsoft identity platform to call Microsoft Graph API from a multi-target console application on National clouds. 21 | 22 | ## About this sample 23 | 24 | ### Overview 25 | 26 | This sample demonstrates a .NET Desktop (Console) application calling Microsoft Graph on National clouds. 27 | 28 | 1. The .NET Desktop (Console) application uses the Microsoft Authentication Library (MSAL) to obtain a JWT access token from a National cloud Microsoft Entra ID 29 | 2. The access token is used as a bearer token to authenticate the user when calling Microsoft Graph. 30 | 31 | ![Overview](./ReadmeFiles/topology.png) 32 | 33 | National clouds (aka Sovereign clouds) are physically isolated instances of Azure. These regions of Azure are designed to make sure that data residency, sovereignty, and compliance requirements are honored within geographical boundaries. 34 | 35 | In addition to the public cloud​, Azure Active Directory is deployed in the following National clouds:   36 | 37 | - Azure US Government 38 | - Azure China 21Vianet 39 | - Azure Germany 40 | 41 | Note that enabling your application for National clouds requires you to: 42 | 43 | - register your application in a specific portal, depending on the cloud 44 | - use a specific authority, depending on the cloud in the config file for your application 45 | - in case you want to call the graph, this requires a specific Graph endpoint URL, depending on the cloud. 46 | 47 | More details in [Authentication in National Clouds](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud) 48 | 49 | ## How to run this sample 50 | 51 | To run this sample, you can follow the same steps on [the first sample in this tutorial](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/1-Calling-MSGraph/1-1-AzureAD) but using the desired National cloud portal to create the application. 52 | 53 | To use the PowerShell script that **automatically** creates the Microsoft Entra application for this sample in your National cloud, please use the parameter `-AzureEnvironmentName` [as described in this document](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/blob/master/1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/AppCreationScripts.md#running-the-script-on-azure-sovereign-clouds). 54 | 55 | ### Configure the client app (Console-Interactive-MultiTarget-v2) to use your app registration 56 | 57 | Open the project in your IDE (like Visual Studio) to configure the code. 58 | 59 | 1. Open the `Console-Interactive-MultiTarget\appsettings.json` file 60 | 1. Find the app key `Instance` and replace the existing value with the [correspondent endpoint for your National clouds](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud#azure-ad-authentication-endpoints). 61 | 1. Find the app key `ClientId` and replace the existing value with the application ID (clientId) of the `Console-Interactive-MultiTarget-v2` application copied from the Microsoft Entra admin center. 62 | 1. Find the app key `TenantId` and replace the existing value with your Microsoft Entra tenant ID. 63 | 1. Find the app key `GraphApiUrl` and replace the existing value with the Microsoft Graph endpoint for your National clouds. [See this reference for more info on which graph endpoint to use](https://docs.microsoft.com/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints). 64 | 65 | ## Community Help and Support 66 | 67 | Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. 68 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. 69 | Make sure that your questions or comments are tagged with [`azure-active-directory` `msal` `dotnet`]. 70 | 71 | If you find a bug in the sample, please raise the issue on [GitHub Issues](../../../../issues). 72 | 73 | To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory). 74 | 75 | > [Consider taking a moment to share your experience with us.](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR73pcsbpbxNJuZCMKN0lURpUREhEVDBOTFBMUVRPUElBUE5WMjdPQ1RaMiQlQCN0PWcu) 76 | 77 | ## Contributing 78 | 79 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md). 80 | 81 | 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. 82 | 83 | ## More information 84 | 85 | For more information, see MSAL.NET's conceptual documentation: 86 | 87 | - [MSAL.NET's conceptual documentation](https://aka.ms/msal-net) 88 | - [Microsoft identity platform (Azure Active Directory for developers)](https://docs.microsoft.com/azure/active-directory/develop/) 89 | - [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) 90 | - [Quickstart: Configure a client application to access web APIs](https://docs.microsoft.com/azure/active-directory/develop/quickstart-configure-app-access-web-apis) 91 | 92 | - [Understanding Microsoft Entra application consent experiences](https://docs.microsoft.com/azure/active-directory/develop/application-consent-experience) 93 | - [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) 94 | - [Application and service principal objects in Microsoft Entra ID](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) 95 | 96 | For more information about how OAuth 2.0 protocols work in this scenario and other scenarios, see [Authentication Scenarios for Microsoft Entra ID](http://go.microsoft.com/fwlink/?LinkId=394414). 97 | -------------------------------------------------------------------------------- /1-Calling-MSGraph/1-3-NationalClouds/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/1-Calling-MSGraph/1-3-NationalClouds/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /2-TokenCache/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory 8 | 1. In PowerShell run: 9 | ```PowerShell 10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 11 | ``` 12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below) 13 | ```PowerShell 14 | cd .\AppCreationScripts\ 15 | .\Configure.ps1 16 | ``` 17 | 1. Open the Visual Studio solution and click start 18 | 19 | ### More details 20 | 21 | The following paragraphs: 22 | 23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 24 | - Explain the [pre-requisites](#pre-requisites) 25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 26 | - [Interactively](#option-1-interactive) to create the app in your home tenant 27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds) 31 | 32 | ## Goal of the scripts 33 | 34 | ### Presentation of the scripts 35 | 36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra 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. 37 | 38 | These scripts are: 39 | 40 | - `Configure.ps1` which: 41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets), 42 | - changes the configuration files in the C# and JavaScript projects. 43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created: 44 | - the identifier of the application 45 | - the AppId of the application 46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 47 | 48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra 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). 49 | 50 | ### Usage pattern for tests and DevOps scenarios 51 | 52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra 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. 53 | 54 | ## How to use the app creation scripts? 55 | 56 | ### Pre-requisites 57 | 58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 59 | 2. Navigate to the root directory of the project. 60 | 3. 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: 61 | ```PowerShell 62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 63 | ``` 64 | ### (Optionally) install AzureAD PowerShell modules 65 | The scripts install the required PowerShell module (AzureAD) 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: 66 | 67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 68 | 69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 70 | 2. Type: 71 | ```PowerShell 72 | Install-Module AzureAD 73 | ``` 74 | 75 | or if you cannot be administrator on your machine, run: 76 | ```PowerShell 77 | Install-Module AzureAD -Scope CurrentUser 78 | ``` 79 | 80 | ### Run the script and start running 81 | 82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 83 | ```PowerShell 84 | cd AppCreationScripts 85 | ``` 86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 88 | 8. select **Start** for the projects 89 | 90 | You're done. this just works! 91 | 92 | ### Four ways to run the script 93 | 94 | We advise four ways of running the script: 95 | 96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 98 | - 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, 99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. 100 | 101 | Here are the details on how to do this. 102 | 103 | #### Option 1 (interactive) 104 | 105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 107 | 108 | 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. 109 | 110 | #### Option 2 (non-interactive) 111 | 112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window 113 | 114 | ```PowerShell 115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 117 | . .\Cleanup.ps1 -Credential $mycreds 118 | . .\Configure.ps1 -Credential $mycreds 119 | ``` 120 | 121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 122 | 123 | #### Option 3 (Interactive, but create apps in a specified tenant) 124 | 125 | if you want to create the apps in a particular tenant, you can use the following option: 126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com) 127 | - Select the Microsoft Entra tenant you are interested in (in the combo-box below your name on the top right of the browser window) 128 | - Find the "Active Directory" object in this tenant 129 | - Go to **Properties** and copy the content of the **Directory Id** property 130 | - Then use the full syntax to run the scripts: 131 | 132 | ```PowerShell 133 | $tenantId = "yourTenantIdGuid" 134 | . .\Cleanup.ps1 -TenantId $tenantId 135 | . .\Configure.ps1 -TenantId $tenantId 136 | ``` 137 | 138 | #### Option 4 (non-interactive, and create apps in a specified tenant) 139 | 140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: 141 | 142 | ```PowerShell 143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 145 | $tenantId = "yourTenantIdGuid" 146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 148 | ``` 149 | 150 | ### Running the script on Azure Sovereign clouds 151 | 152 | 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`. 153 | 154 | The acceptable values for this parameter are: 155 | 156 | - AzureCloud 157 | - AzureChinaCloud 158 | - AzureUSGovernment 159 | - AzureGermanyCloud 160 | 161 | Example: 162 | 163 | ```PowerShell 164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud" 165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud" 166 | ``` 167 | -------------------------------------------------------------------------------- /2-TokenCache/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 7 | [string] $azureEnvironmentName 8 | ) 9 | 10 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) { 11 | Install-Module "AzureAD" -Scope CurrentUser 12 | } 13 | Import-Module AzureAD 14 | $ErrorActionPreference = "Stop" 15 | 16 | Function Cleanup 17 | { 18 | if (!$azureEnvironmentName) 19 | { 20 | $azureEnvironmentName = "AzureCloud" 21 | } 22 | 23 | <# 24 | .Description 25 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 26 | #> 27 | 28 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 29 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 30 | 31 | # Login to Azure PowerShell (interactive if credentials are not already provided: 32 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 33 | if (!$Credential -and $TenantId) 34 | { 35 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 36 | } 37 | else 38 | { 39 | if (!$TenantId) 40 | { 41 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 42 | } 43 | else 44 | { 45 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 46 | } 47 | } 48 | 49 | if (!$tenantId) 50 | { 51 | $tenantId = $creds.Tenant.Id 52 | } 53 | $tenant = Get-AzureADTenantDetail 54 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name 55 | 56 | # Removes the applications 57 | Write-Host "Cleaning-up applications from tenant '$tenantName'" 58 | 59 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed" 60 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 61 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" 62 | if ($apps) 63 | { 64 | Remove-AzureADApplication -ObjectId $apps.ObjectId 65 | } 66 | 67 | foreach ($app in $apps) 68 | { 69 | Remove-AzureADApplication -ObjectId $app.ObjectId 70 | Write-Host "Removed Console-Interactive-MultiTarget-v2.." 71 | } 72 | # also remove service principals of this app 73 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 74 | 75 | } 76 | 77 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /2-TokenCache/AppCreationScripts/Configure.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 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 | Before running this script you need to install the AzureAD cmdlets as an administrator. 15 | For this: 16 | 1) Run Powershell as an administrator 17 | 2) in the PowerShell window, type: Install-Module AzureAD 18 | 19 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. 20 | #> 21 | 22 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure 23 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is 24 | # described in $permissionType 25 | Function AddResourcePermission($requiredAccess, ` 26 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) 27 | { 28 | foreach($permission in $requiredAccesses.Trim().Split("|")) 29 | { 30 | foreach($exposedPermission in $exposedPermissions) 31 | { 32 | if ($exposedPermission.Value -eq $permission) 33 | { 34 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 35 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions 36 | $resourceAccess.Id = $exposedPermission.Id # Read directory data 37 | $requiredAccess.ResourceAccess.Add($resourceAccess) 38 | } 39 | } 40 | } 41 | } 42 | 43 | # 44 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" 45 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell 46 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) 47 | { 48 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique) 49 | if ($servicePrincipal) 50 | { 51 | $sp = $servicePrincipal 52 | } 53 | else 54 | { 55 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" 56 | } 57 | $appid = $sp.AppId 58 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 59 | $requiredAccess.ResourceAppId = $appid 60 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 61 | 62 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: 63 | if ($requiredDelegatedPermissions) 64 | { 65 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 66 | } 67 | 68 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application 69 | if ($requiredApplicationPermissions) 70 | { 71 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 72 | } 73 | return $requiredAccess 74 | } 75 | 76 | 77 | Function UpdateLine([string] $line, [string] $value) 78 | { 79 | $index = $line.IndexOf('=') 80 | $delimiter = ';' 81 | if ($index -eq -1) 82 | { 83 | $index = $line.IndexOf(':') 84 | $delimiter = ',' 85 | } 86 | if ($index -ige 0) 87 | { 88 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter 89 | } 90 | return $line 91 | } 92 | 93 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary) 94 | { 95 | $lines = Get-Content $configFilePath 96 | $index = 0 97 | while($index -lt $lines.Length) 98 | { 99 | $line = $lines[$index] 100 | foreach($key in $dictionary.Keys) 101 | { 102 | if ($line.Contains($key)) 103 | { 104 | $lines[$index] = UpdateLine $line $dictionary[$key] 105 | } 106 | } 107 | $index++ 108 | } 109 | 110 | Set-Content -Path $configFilePath -Value $lines -Force 111 | } 112 | 113 | Set-Content -Value "" -Path createdApps.html 114 | Add-Content -Value "" -Path createdApps.html 115 | 116 | $ErrorActionPreference = "Stop" 117 | 118 | Function ConfigureApplications 119 | { 120 | <#.Description 121 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the 122 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) 123 | so that they are consistent with the Applications parameters 124 | #> 125 | $commonendpoint = "common" 126 | 127 | if (!$azureEnvironmentName) 128 | { 129 | $azureEnvironmentName = "AzureCloud" 130 | } 131 | 132 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 133 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 134 | 135 | # Login to Azure PowerShell (interactive if credentials are not already provided: 136 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 137 | if (!$Credential -and $TenantId) 138 | { 139 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 140 | } 141 | else 142 | { 143 | if (!$TenantId) 144 | { 145 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 146 | } 147 | else 148 | { 149 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 150 | } 151 | } 152 | 153 | if (!$tenantId) 154 | { 155 | $tenantId = $creds.Tenant.Id 156 | } 157 | 158 | 159 | 160 | $tenant = Get-AzureADTenantDetail 161 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name 162 | 163 | # Get the user running the script to add the user as the app owner 164 | $user = Get-AzureADUser -ObjectId $creds.Account.Id 165 | 166 | # Create the client AAD application 167 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)" 168 | # create the application 169 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" ` 170 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" ` 171 | -AvailableToOtherTenants $True ` 172 | -PublicClient $True 173 | 174 | # create the service principal of the newly created application 175 | $currentAppId = $clientAadApplication.AppId 176 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} 177 | 178 | # add the user running the script as an app owner if needed 179 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId 180 | if ($owner -eq $null) 181 | { 182 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId 183 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'" 184 | } 185 | 186 | 187 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)" 188 | 189 | # URL of the AAD application in the Azure portal 190 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 191 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 192 | Add-Content -Value "" -Path createdApps.html 193 | 194 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 195 | 196 | # Add Required Resources Access (from 'client' to 'Microsoft Graph') 197 | Write-Host "Getting access from 'client' to 'Microsoft Graph'" 198 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` 199 | -requiredDelegatedPermissions "User.Read" ` 200 | 201 | $requiredResourcesAccess.Add($requiredPermissions) 202 | 203 | 204 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess 205 | Write-Host "Granted permissions." 206 | 207 | # Update config file for 'client' 208 | $configFile = $pwd.Path + "\..\Console-TokenCache\appsettings.json" 209 | Write-Host "Updating the sample code ($configFile)" 210 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId }; 211 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary 212 | 213 | Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
client$currentAppIdConsole-Interactive-MultiTarget-v2
" -Path createdApps.html 214 | } 215 | 216 | # Pre-requisites 217 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { 218 | Install-Module "AzureAD" -Scope CurrentUser 219 | } 220 | 221 | Import-Module AzureAD 222 | 223 | # Run interactively (will ask you for the tenant ID) 224 | ConfigureApplications -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /2-TokenCache/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API from a multi-target console application, with token cache", 4 | "Level": 200, 5 | "Client": ".NET Desktop (Console)", 6 | "Service": "Microsoft Graph", 7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial", 8 | "Endpoint": "AAD v2.0" 9 | }, 10 | 11 | /* 12 | This section describes the Azure AD Applications to configure, and their dependencies 13 | */ 14 | "AADApps": [ 15 | { 16 | "Id": "client", 17 | "Name": "Console-Interactive-MultiTarget-v2", 18 | "Kind": "Desktop", 19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost", 20 | "RequiredResourcesAccess": [ 21 | { 22 | "Resource": "Microsoft Graph", 23 | "DelegatedPermissions": [ "User.Read" ] 24 | } 25 | ] 26 | } 27 | ], 28 | 29 | /* 30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 31 | are created in Azure AD. 32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 34 | */ 35 | "CodeConfiguration": [ 36 | { 37 | "App": "client", 38 | "SettingKind": "JSon", 39 | "SettingFile": "\\..\\Console-TokenCache\\appsettings.json", 40 | "Mappings": [ 41 | { 42 | "key": "ClientId", 43 | "value": ".AppId" 44 | }, 45 | { 46 | "key": "TenantId", 47 | "value": "$tenantId" 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /2-TokenCache/Console-TokenCache.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29424.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console-TokenCache", "Console-TokenCache\Console-TokenCache.csproj", "{9286F81F-F57A-4B43-99AD-A7789771EC4C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {305EF8E8-BB35-44FD-A26D-546A38F197EC} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /2-TokenCache/Console-TokenCache/CacheSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client.Extensions.Msal; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Console_TokenCache 6 | { 7 | public static class CacheSettings 8 | { 9 | // computing the root directory is not very simple on Linux and Mac, so a helper is provided 10 | private static readonly string s_cacheFilePath = 11 | Path.Combine(MsalCacheHelper.UserRootDirectory, "msal.contoso.cache"); 12 | 13 | public static readonly string CacheFileName = Path.GetFileName(s_cacheFilePath); 14 | public static readonly string CacheDir = Path.GetDirectoryName(s_cacheFilePath); 15 | 16 | 17 | public static readonly string KeyChainServiceName = "Contoso.MyProduct"; 18 | public static readonly string KeyChainAccountName = "MSALCache"; 19 | 20 | public static readonly string LinuxKeyRingSchema = "com.contoso.devtools.tokencache"; 21 | public static readonly string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection; 22 | public static readonly string LinuxKeyRingLabel = "MSAL token cache for all Contoso dev tool apps."; 23 | public static readonly KeyValuePair LinuxKeyRingAttr1 = new KeyValuePair("Version", "1"); 24 | public static readonly KeyValuePair LinuxKeyRingAttr2 = new KeyValuePair("ProductGroup", "MyApps"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /2-TokenCache/Console-TokenCache/Console-TokenCache.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1; net472 6 | Console_TokenCache 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /2-TokenCache/Console-TokenCache/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Graph; 3 | using Microsoft.Identity.Client; 4 | using Microsoft.Identity.Client.Extensions.Msal; 5 | using System; 6 | using System.Linq; 7 | using System.Net.Http.Headers; 8 | using System.Threading.Tasks; 9 | 10 | namespace Console_TokenCache 11 | { 12 | class Program 13 | { 14 | private static PublicClientApplicationOptions appConfiguration = null; 15 | private static IConfiguration configuration; 16 | private static string _authority; 17 | 18 | static async Task Main(string[] _) 19 | { 20 | // Using appsettings.json as our configuration settings 21 | var builder = new ConfigurationBuilder() 22 | .SetBasePath(System.IO.Directory.GetCurrentDirectory()) 23 | .AddJsonFile("appsettings.json"); 24 | 25 | configuration = builder.Build(); 26 | 27 | // Loading PublicClientApplicationOptions from the values set on appsettings.json 28 | appConfiguration = configuration 29 | .Get(); 30 | 31 | // Building the AAD authority, https://login.microsoftonline.com/ 32 | _authority = string.Concat(appConfiguration.Instance, appConfiguration.TenantId); 33 | 34 | // Building a public client application 35 | var app = PublicClientApplicationBuilder.Create(appConfiguration.ClientId) 36 | .WithAuthority(_authority) 37 | .WithRedirectUri(appConfiguration.RedirectUri) 38 | .Build(); 39 | 40 | // Building StorageCreationProperties 41 | var storageProperties = 42 | new StorageCreationPropertiesBuilder(CacheSettings.CacheFileName, CacheSettings.CacheDir, appConfiguration.ClientId) 43 | .WithLinuxKeyring( 44 | CacheSettings.LinuxKeyRingSchema, 45 | CacheSettings.LinuxKeyRingCollection, 46 | CacheSettings.LinuxKeyRingLabel, 47 | CacheSettings.LinuxKeyRingAttr1, 48 | CacheSettings.LinuxKeyRingAttr2) 49 | .WithMacKeyChain( 50 | CacheSettings.KeyChainServiceName, 51 | CacheSettings.KeyChainAccountName) 52 | .Build(); 53 | 54 | // This hooks up the cross-platform cache into MSAL 55 | var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties); 56 | cacheHelper.RegisterCache(app.UserTokenCache); 57 | 58 | // Scope for Microsoft Graph 59 | string[] scopes = new[] { "user.read" }; 60 | string graphApiUrl = configuration.GetValue("GraphApiUrl"); 61 | 62 | AuthenticationResult result; 63 | GraphServiceClient graphClient; 64 | User me; 65 | 66 | while (true) 67 | { 68 | // Display menu 69 | Console.WriteLine("------------ MENU ------------"); 70 | Console.WriteLine("1. Acquire Token Silent / Interactive (not using embedded view)"); 71 | Console.WriteLine("2. Acquire Token Silent / Interactive (using embedded view, currently not supported on .NET Core)"); 72 | Console.WriteLine("3. Display Accounts (reads the cache)"); 73 | Console.WriteLine("4. Clear cache"); 74 | Console.WriteLine("x. Exit app"); 75 | Console.Write("Enter your Selection:"); 76 | char.TryParse(Console.ReadLine(), out var selection); 77 | 78 | try 79 | { 80 | switch (selection) 81 | { 82 | case '1': // Silent / Interactive 83 | Console.Clear(); 84 | Console.WriteLine("Acquiring token from the cache (silently), if it fails do it interactively"); 85 | 86 | result = await AcquireToken(app, scopes, false); 87 | 88 | graphClient = GetGraphServiceClient(result.AccessToken, graphApiUrl); 89 | me = await graphClient.Me.Request().GetAsync(); 90 | 91 | DisplayGraphResult(result, me); 92 | break; 93 | 94 | case '2': // Silent / Interactive with embedded view 95 | Console.Clear(); 96 | Console.WriteLine("Acquiring token from the cache (silently), if it fails do it interactively using embedded view"); 97 | 98 | result = await AcquireToken(app, scopes, true); 99 | 100 | graphClient = GetGraphServiceClient(result.AccessToken, graphApiUrl); 101 | me = await graphClient.Me.Request().GetAsync(); 102 | 103 | DisplayGraphResult(result, me); 104 | break; 105 | 106 | case '3': // Display Accounts 107 | Console.Clear(); 108 | var accounts2 = await app.GetAccountsAsync().ConfigureAwait(false); 109 | if (!accounts2.Any()) 110 | { 111 | Console.WriteLine("No accounts were found in the cache."); 112 | Console.Write(Environment.NewLine); 113 | } 114 | 115 | foreach (var acc in accounts2) 116 | { 117 | Console.WriteLine($"Account for {acc.Username}"); 118 | Console.Write(Environment.NewLine); 119 | } 120 | break; 121 | 122 | case '4': // Clear cache 123 | Console.Clear(); 124 | var accounts3 = await app.GetAccountsAsync().ConfigureAwait(false); 125 | foreach (var acc in accounts3) 126 | { 127 | Console.WriteLine($"Removing account for {acc.Username}"); 128 | Console.Write(Environment.NewLine); 129 | await app.RemoveAsync(acc).ConfigureAwait(false); 130 | } 131 | break; 132 | 133 | case 'x': 134 | return; 135 | } 136 | 137 | } 138 | catch (Exception ex) 139 | { 140 | Console.ForegroundColor = ConsoleColor.Red; 141 | Console.WriteLine("Exception : " + ex); 142 | Console.ResetColor(); 143 | Console.WriteLine("Hit Enter to continue"); 144 | Console.Read(); 145 | Console.Clear(); 146 | } 147 | } 148 | } 149 | 150 | private static async Task AcquireToken(IPublicClientApplication app, string[] scopes, bool useEmbaddedView) 151 | { 152 | AuthenticationResult result; 153 | try 154 | { 155 | var accounts = await app.GetAccountsAsync(); 156 | 157 | // Try to acquire an access token from the cache. If an interaction is required, 158 | // MsalUiRequiredException will be thrown. 159 | result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) 160 | .ExecuteAsync(); 161 | } 162 | catch (MsalUiRequiredException) 163 | { 164 | // Acquiring an access token interactively. MSAL will cache it so we can use AcquireTokenSilent 165 | // on future calls. 166 | result = await app.AcquireTokenInteractive(scopes) 167 | .WithUseEmbeddedWebView(useEmbaddedView) 168 | .ExecuteAsync(); 169 | } 170 | 171 | return result; 172 | } 173 | 174 | private static GraphServiceClient GetGraphServiceClient(string accessToken, string graphApiUrl) 175 | { 176 | GraphServiceClient graphServiceClient = new GraphServiceClient(graphApiUrl, 177 | new DelegateAuthenticationProvider( 178 | async (requestMessage) => 179 | { 180 | await Task.Run(() => 181 | { 182 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); 183 | }); 184 | })); 185 | 186 | return graphServiceClient; 187 | } 188 | 189 | private static void DisplayGraphResult(AuthenticationResult result, User me) 190 | { 191 | Console.ForegroundColor = ConsoleColor.Green; 192 | 193 | Console.Write(Environment.NewLine); 194 | Console.WriteLine($"Hello {result.Account.Username}"); 195 | Console.Write(Environment.NewLine); 196 | Console.WriteLine("-------- GRAPH RESULT --------"); 197 | Console.Write(Environment.NewLine); 198 | Console.WriteLine($"Id: {me.Id}"); 199 | Console.WriteLine($"Display Name: {me.DisplayName}"); 200 | Console.WriteLine($"Email: {me.Mail}"); 201 | Console.Write(Environment.NewLine); 202 | Console.WriteLine("------------------------------"); 203 | Console.Write(Environment.NewLine); 204 | Console.Write(Environment.NewLine); 205 | Console.ResetColor(); 206 | 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /2-TokenCache/Console-TokenCache/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Instance": "https://login.microsoftonline.com/", 3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]", 4 | "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]", 5 | "RedirectUri": "http://localhost", 6 | "GraphApiUrl": "https://graph.microsoft.com/beta/" 7 | } 8 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory 8 | 1. In PowerShell run: 9 | ```PowerShell 10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 11 | ``` 12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below) 13 | ```PowerShell 14 | cd .\AppCreationScripts\ 15 | .\Configure.ps1 16 | ``` 17 | 1. Open the Visual Studio solution and click start 18 | 19 | ### More details 20 | 21 | The following paragraphs: 22 | 23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 24 | - Explain the [pre-requisites](#pre-requisites) 25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 26 | - [Interactively](#option-1-interactive) to create the app in your home tenant 27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds) 31 | 32 | ## Goal of the scripts 33 | 34 | ### Presentation of the scripts 35 | 36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra 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. 37 | 38 | These scripts are: 39 | 40 | - `Configure.ps1` which: 41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets), 42 | - changes the configuration files in the C# and JavaScript projects. 43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created: 44 | - the identifier of the application 45 | - the AppId of the application 46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 47 | 48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra 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). 49 | 50 | ### Usage pattern for tests and DevOps scenarios 51 | 52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra 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. 53 | 54 | ## How to use the app creation scripts? 55 | 56 | ### Pre-requisites 57 | 58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 59 | 2. Navigate to the root directory of the project. 60 | 3. 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: 61 | ```PowerShell 62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 63 | ``` 64 | ### (Optionally) install AzureAD PowerShell modules 65 | The scripts install the required PowerShell module (AzureAD) 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: 66 | 67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 68 | 69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 70 | 2. Type: 71 | ```PowerShell 72 | Install-Module AzureAD 73 | ``` 74 | 75 | or if you cannot be administrator on your machine, run: 76 | ```PowerShell 77 | Install-Module AzureAD -Scope CurrentUser 78 | ``` 79 | 80 | ### Run the script and start running 81 | 82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 83 | ```PowerShell 84 | cd AppCreationScripts 85 | ``` 86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 88 | 8. select **Start** for the projects 89 | 90 | You're done. this just works! 91 | 92 | ### Four ways to run the script 93 | 94 | We advise four ways of running the script: 95 | 96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 98 | - 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, 99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. 100 | 101 | Here are the details on how to do this. 102 | 103 | #### Option 1 (interactive) 104 | 105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 107 | 108 | 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. 109 | 110 | #### Option 2 (non-interactive) 111 | 112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window 113 | 114 | ```PowerShell 115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 117 | . .\Cleanup.ps1 -Credential $mycreds 118 | . .\Configure.ps1 -Credential $mycreds 119 | ``` 120 | 121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 122 | 123 | #### Option 3 (Interactive, but create apps in a specified tenant) 124 | 125 | if you want to create the apps in a particular tenant, you can use the following option: 126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com) 127 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 128 | - Find the "Active Directory" object in this tenant 129 | - Go to **Properties** and copy the content of the **Directory Id** property 130 | - Then use the full syntax to run the scripts: 131 | 132 | ```PowerShell 133 | $tenantId = "yourTenantIdGuid" 134 | . .\Cleanup.ps1 -TenantId $tenantId 135 | . .\Configure.ps1 -TenantId $tenantId 136 | ``` 137 | 138 | #### Option 4 (non-interactive, and create apps in a specified tenant) 139 | 140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: 141 | 142 | ```PowerShell 143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 145 | $tenantId = "yourTenantIdGuid" 146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 148 | ``` 149 | 150 | ### Running the script on Azure Sovereign clouds 151 | 152 | 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`. 153 | 154 | The acceptable values for this parameter are: 155 | 156 | - AzureCloud 157 | - AzureChinaCloud 158 | - AzureUSGovernment 159 | - AzureGermanyCloud 160 | 161 | Example: 162 | 163 | ```PowerShell 164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud" 165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud" 166 | ``` 167 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 7 | [string] $azureEnvironmentName 8 | ) 9 | 10 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) { 11 | Install-Module "AzureAD" -Scope CurrentUser 12 | } 13 | Import-Module AzureAD 14 | $ErrorActionPreference = "Stop" 15 | 16 | Function Cleanup 17 | { 18 | if (!$azureEnvironmentName) 19 | { 20 | $azureEnvironmentName = "AzureCloud" 21 | } 22 | 23 | <# 24 | .Description 25 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 26 | #> 27 | 28 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 29 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 30 | 31 | # Login to Azure PowerShell (interactive if credentials are not already provided: 32 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 33 | if (!$Credential -and $TenantId) 34 | { 35 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 36 | } 37 | else 38 | { 39 | if (!$TenantId) 40 | { 41 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 42 | } 43 | else 44 | { 45 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 46 | } 47 | } 48 | 49 | if (!$tenantId) 50 | { 51 | $tenantId = $creds.Tenant.Id 52 | } 53 | $tenant = Get-AzureADTenantDetail 54 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name 55 | 56 | # Removes the applications 57 | Write-Host "Cleaning-up applications from tenant '$tenantName'" 58 | 59 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed" 60 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 61 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" 62 | if ($apps) 63 | { 64 | Remove-AzureADApplication -ObjectId $apps.ObjectId 65 | } 66 | 67 | foreach ($app in $apps) 68 | { 69 | Remove-AzureADApplication -ObjectId $app.ObjectId 70 | Write-Host "Removed Console-Interactive-MultiTarget-v2.." 71 | } 72 | # also remove service principals of this app 73 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 74 | 75 | } 76 | 77 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/Configure.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 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 | Before running this script you need to install the AzureAD cmdlets as an administrator. 15 | For this: 16 | 1) Run Powershell as an administrator 17 | 2) in the PowerShell window, type: Install-Module AzureAD 18 | 19 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. 20 | #> 21 | 22 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure 23 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is 24 | # described in $permissionType 25 | Function AddResourcePermission($requiredAccess, ` 26 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) 27 | { 28 | foreach($permission in $requiredAccesses.Trim().Split("|")) 29 | { 30 | foreach($exposedPermission in $exposedPermissions) 31 | { 32 | if ($exposedPermission.Value -eq $permission) 33 | { 34 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 35 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions 36 | $resourceAccess.Id = $exposedPermission.Id # Read directory data 37 | $requiredAccess.ResourceAccess.Add($resourceAccess) 38 | } 39 | } 40 | } 41 | } 42 | 43 | # 44 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" 45 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell 46 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) 47 | { 48 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique) 49 | if ($servicePrincipal) 50 | { 51 | $sp = $servicePrincipal 52 | } 53 | else 54 | { 55 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" 56 | } 57 | $appid = $sp.AppId 58 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 59 | $requiredAccess.ResourceAppId = $appid 60 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 61 | 62 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: 63 | if ($requiredDelegatedPermissions) 64 | { 65 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 66 | } 67 | 68 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application 69 | if ($requiredApplicationPermissions) 70 | { 71 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 72 | } 73 | return $requiredAccess 74 | } 75 | 76 | 77 | Function UpdateLine([string] $line, [string] $value) 78 | { 79 | $index = $line.IndexOf('=') 80 | $delimiter = ';' 81 | if ($index -eq -1) 82 | { 83 | $index = $line.IndexOf(':') 84 | $delimiter = ',' 85 | } 86 | if ($index -ige 0) 87 | { 88 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter 89 | } 90 | return $line 91 | } 92 | 93 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary) 94 | { 95 | $lines = Get-Content $configFilePath 96 | $index = 0 97 | while($index -lt $lines.Length) 98 | { 99 | $line = $lines[$index] 100 | foreach($key in $dictionary.Keys) 101 | { 102 | if ($line.Contains($key)) 103 | { 104 | $lines[$index] = UpdateLine $line $dictionary[$key] 105 | } 106 | } 107 | $index++ 108 | } 109 | 110 | Set-Content -Path $configFilePath -Value $lines -Force 111 | } 112 | 113 | Set-Content -Value "" -Path createdApps.html 114 | Add-Content -Value "" -Path createdApps.html 115 | 116 | $ErrorActionPreference = "Stop" 117 | 118 | Function ConfigureApplications 119 | { 120 | <#.Description 121 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the 122 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) 123 | so that they are consistent with the Applications parameters 124 | #> 125 | $commonendpoint = "common" 126 | 127 | if (!$azureEnvironmentName) 128 | { 129 | $azureEnvironmentName = "AzureCloud" 130 | } 131 | 132 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 133 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 134 | 135 | # Login to Azure PowerShell (interactive if credentials are not already provided: 136 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 137 | if (!$Credential -and $TenantId) 138 | { 139 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 140 | } 141 | else 142 | { 143 | if (!$TenantId) 144 | { 145 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 146 | } 147 | else 148 | { 149 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 150 | } 151 | } 152 | 153 | if (!$tenantId) 154 | { 155 | $tenantId = $creds.Tenant.Id 156 | } 157 | 158 | 159 | 160 | $tenant = Get-AzureADTenantDetail 161 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name 162 | 163 | # Get the user running the script to add the user as the app owner 164 | $user = Get-AzureADUser -ObjectId $creds.Account.Id 165 | 166 | # Create the client AAD application 167 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)" 168 | # create the application 169 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" ` 170 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" ` 171 | -AvailableToOtherTenants $True ` 172 | -PublicClient $True 173 | 174 | # create the service principal of the newly created application 175 | $currentAppId = $clientAadApplication.AppId 176 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} 177 | 178 | # add the user running the script as an app owner if needed 179 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId 180 | if ($owner -eq $null) 181 | { 182 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId 183 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'" 184 | } 185 | 186 | 187 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)" 188 | 189 | # URL of the AAD application in the Azure portal 190 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 191 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 192 | Add-Content -Value "" -Path createdApps.html 193 | 194 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 195 | 196 | # Add Required Resources Access (from 'client' to 'Microsoft Graph') 197 | Write-Host "Getting access from 'client' to 'Microsoft Graph'" 198 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` 199 | -requiredDelegatedPermissions "User.Read" ` 200 | 201 | $requiredResourcesAccess.Add($requiredPermissions) 202 | 203 | 204 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess 205 | Write-Host "Granted permissions." 206 | 207 | # Update config file for 'client' 208 | $configFile = $pwd.Path + "\..\Console-Interactive-CustomWebUI\appsettings.json" 209 | Write-Host "Updating the sample code ($configFile)" 210 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId }; 211 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary 212 | 213 | Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
client$currentAppIdConsole-Interactive-MultiTarget-v2
" -Path createdApps.html 214 | } 215 | 216 | # Pre-requisites 217 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { 218 | Install-Module "AzureAD" -Scope CurrentUser 219 | } 220 | 221 | Import-Module AzureAD 222 | 223 | # Run interactively (will ask you for the tenant ID) 224 | ConfigureApplications -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API with custom web ui.", 4 | "Level": 300, 5 | "Client": ".NET Desktop (Console)", 6 | "Service": "Microsoft Graph", 7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial", 8 | "Endpoint": "AAD v2.0" 9 | }, 10 | 11 | /* 12 | This section describes the Azure AD Applications to configure, and their dependencies 13 | */ 14 | "AADApps": [ 15 | { 16 | "Id": "client", 17 | "Name": "Console-Interactive-MultiTarget-v2", 18 | "Kind": "Desktop", 19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost", 20 | "RequiredResourcesAccess": [ 21 | { 22 | "Resource": "Microsoft Graph", 23 | "DelegatedPermissions": [ "User.Read" ] 24 | } 25 | ] 26 | } 27 | ], 28 | 29 | /* 30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 31 | are created in Azure AD. 32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 34 | */ 35 | "CodeConfiguration": [ 36 | { 37 | "App": "client", 38 | "SettingKind": "JSon", 39 | "SettingFile": "\\..\\Console-Interactive-CustomWebUI\\appsettings.json", 40 | "Mappings": [ 41 | { 42 | "key": "ClientId", 43 | "value": ".AppId" 44 | }, 45 | { 46 | "key": "TenantId", 47 | "value": "$tenantId" 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/Console-Interactive-CustomWebUI/Console-Interactive-CustomWebUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1; net472 6 | Console_Interactive_CustomWebUI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/Console-Interactive-CustomWebUI/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Graph; 3 | using Microsoft.Identity.Client; 4 | using System; 5 | using System.Linq; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | 9 | namespace Console_Interactive_CustomWebUI 10 | { 11 | class Program 12 | { 13 | private static PublicClientApplicationOptions appConfiguration = null; 14 | private static IConfiguration configuration; 15 | private static string graphURL; 16 | 17 | // The MSAL Public client app 18 | private static IPublicClientApplication application; 19 | 20 | // Object with the custom HTML 21 | private static SystemWebViewOptions _customWebView = GetCustomHTML(); 22 | 23 | static async Task Main(string[] args) 24 | { 25 | // Using appsettings.json as our configuration settings 26 | var builder = new ConfigurationBuilder() 27 | .SetBasePath(System.IO.Directory.GetCurrentDirectory()) 28 | .AddJsonFile("appsettings.json"); 29 | 30 | configuration = builder.Build(); 31 | 32 | appConfiguration = configuration 33 | .Get(); 34 | 35 | // We intend to obtain a token for Graph for the following scopes (permissions) 36 | string[] scopes = new[] { "user.read" }; 37 | 38 | graphURL = configuration.GetValue("GraphApiUrl"); 39 | 40 | // Sign-in user using MSAL and obtain an access token for MS Graph 41 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes); 42 | 43 | // Call the /me endpoint of MS Graph 44 | await CallMSGraph(graphClient); 45 | 46 | Console.ReadKey(); 47 | } 48 | 49 | /// 50 | /// Sign in user using MSAL and obtain a token for MS Graph 51 | /// 52 | /// 53 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes) 54 | { 55 | GraphServiceClient graphClient = new GraphServiceClient(graphURL, 56 | new DelegateAuthenticationProvider(async (requestMessage) => 57 | { 58 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes)); 59 | })); 60 | 61 | return await Task.FromResult(graphClient); 62 | } 63 | 64 | /// 65 | /// Signs in the user using the device code flow and obtains an Access token for MS Graph 66 | /// 67 | /// 68 | /// 69 | /// 70 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes) 71 | { 72 | // build the AAd authority Url 73 | string authority = string.Concat(configuration.Instance, configuration.TenantId); 74 | 75 | // Initialize the MSAL library by building a public client application 76 | application = PublicClientApplicationBuilder.Create(configuration.ClientId) 77 | .WithAuthority(authority) 78 | .WithRedirectUri(configuration.RedirectUri) 79 | .Build(); 80 | 81 | 82 | AuthenticationResult result; 83 | 84 | try 85 | { 86 | var accounts = await application.GetAccountsAsync(); 87 | 88 | // Try to acquire an access token from the cache, if UI interaction is required, MsalUiRequiredException will be thrown. 89 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync(); 90 | } 91 | catch (MsalUiRequiredException) 92 | { 93 | // Acquiring an access token interactively using the custom html. 94 | result = await application.AcquireTokenInteractive(scopes) 95 | .WithSystemWebViewOptions(_customWebView) // Using the custom html 96 | .ExecuteAsync(); 97 | } 98 | 99 | return result.AccessToken; 100 | } 101 | 102 | /// 103 | /// Call MS Graph and print results 104 | /// 105 | /// 106 | /// 107 | private static async Task CallMSGraph(GraphServiceClient graphClient) 108 | { 109 | var me = await graphClient.Me.Request().GetAsync(); 110 | 111 | // Printing the results 112 | Console.Write(Environment.NewLine); 113 | Console.WriteLine("-------- Data from call to MS Graph --------"); 114 | Console.Write(Environment.NewLine); 115 | Console.WriteLine($"Id: {me.Id}"); 116 | Console.WriteLine($"Display Name: {me.DisplayName}"); 117 | Console.WriteLine($"Email: {me.Mail}"); 118 | } 119 | 120 | /// 121 | /// Returns a custom HTML for the authorization success or failure, and redirect url. 122 | /// For more available options, please inspect the SystemWebViewOptions class. 123 | /// 124 | /// 125 | private static SystemWebViewOptions GetCustomHTML() 126 | { 127 | return new SystemWebViewOptions 128 | { 129 | HtmlMessageSuccess = @" 130 | Authentication Complete 131 | 132 |
133 |

Custom Web UI

134 |
135 |
136 |

Authentication complete

137 |
You can return to the application. Feel free to close this browser tab.
138 |
139 | 140 | 141 | ", 142 | 143 | HtmlMessageError = @" 144 | Authentication Failed 145 | 146 |
147 |

Custom Web UI

148 |
149 |
150 |

Authentication failed

151 |
Error details: error {0} error_description: {1}
152 |
153 |
You can return to the application. Feel free to close this browser tab.
154 |
155 | 156 | 157 | " 158 | }; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/Console-Interactive-CustomWebUI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Instance": "https://login.microsoftonline.com/", 3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]", 4 | "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]", 5 | "RedirectUri": "http://localhost", 6 | "GraphApiUrl": "https://graph.microsoft.com/beta/" 7 | } 8 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/Console-Interactive.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console-Interactive-CustomWebUI", "Console-Interactive-CustomWebUI\Console-Interactive-CustomWebUI.csproj", "{509D5688-3BD1-4650-8A43-3CEF5B64198E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {42EE218E-9522-4DBA-9DFE-2EC0FB0CD3D5} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/failureMessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/failureMessage.png -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/successMessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/successMessage.png -------------------------------------------------------------------------------- /3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory 8 | 1. In PowerShell run: 9 | ```PowerShell 10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 11 | ``` 12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below) 13 | ```PowerShell 14 | cd .\AppCreationScripts\ 15 | .\Configure.ps1 16 | ``` 17 | 1. Open the Visual Studio solution and click start 18 | 19 | ### More details 20 | 21 | The following paragraphs: 22 | 23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 24 | - Explain the [pre-requisites](#pre-requisites) 25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 26 | - [Interactively](#option-1-interactive) to create the app in your home tenant 27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds) 31 | 32 | ## Goal of the scripts 33 | 34 | ### Presentation of the scripts 35 | 36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra 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. 37 | 38 | These scripts are: 39 | 40 | - `Configure.ps1` which: 41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets), 42 | - changes the configuration files in the C# and JavaScript projects. 43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created: 44 | - the identifier of the application 45 | - the AppId of the application 46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 47 | 48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra 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). 49 | 50 | ### Usage pattern for tests and DevOps scenarios 51 | 52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra 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. 53 | 54 | ## How to use the app creation scripts? 55 | 56 | ### Pre-requisites 57 | 58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 59 | 2. Navigate to the root directory of the project. 60 | 3. 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: 61 | ```PowerShell 62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 63 | ``` 64 | ### (Optionally) install AzureAD PowerShell modules 65 | The scripts install the required PowerShell module (AzureAD) 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: 66 | 67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 68 | 69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 70 | 2. Type: 71 | ```PowerShell 72 | Install-Module AzureAD 73 | ``` 74 | 75 | or if you cannot be administrator on your machine, run: 76 | ```PowerShell 77 | Install-Module AzureAD -Scope CurrentUser 78 | ``` 79 | 80 | ### Run the script and start running 81 | 82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 83 | ```PowerShell 84 | cd AppCreationScripts 85 | ``` 86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 88 | 8. select **Start** for the projects 89 | 90 | You're done. this just works! 91 | 92 | ### Four ways to run the script 93 | 94 | We advise four ways of running the script: 95 | 96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 98 | - 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, 99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. 100 | 101 | Here are the details on how to do this. 102 | 103 | #### Option 1 (interactive) 104 | 105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 107 | 108 | 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. 109 | 110 | #### Option 2 (non-interactive) 111 | 112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window 113 | 114 | ```PowerShell 115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 117 | . .\Cleanup.ps1 -Credential $mycreds 118 | . .\Configure.ps1 -Credential $mycreds 119 | ``` 120 | 121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 122 | 123 | #### Option 3 (Interactive, but create apps in a specified tenant) 124 | 125 | if you want to create the apps in a particular tenant, you can use the following option: 126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com) 127 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 128 | - Find the "Active Directory" object in this tenant 129 | - Go to **Properties** and copy the content of the **Directory Id** property 130 | - Then use the full syntax to run the scripts: 131 | 132 | ```PowerShell 133 | $tenantId = "yourTenantIdGuid" 134 | . .\Cleanup.ps1 -TenantId $tenantId 135 | . .\Configure.ps1 -TenantId $tenantId 136 | ``` 137 | 138 | #### Option 4 (non-interactive, and create apps in a specified tenant) 139 | 140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: 141 | 142 | ```PowerShell 143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 145 | $tenantId = "yourTenantIdGuid" 146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 148 | ``` 149 | 150 | ### Running the script on Azure Sovereign clouds 151 | 152 | 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`. 153 | 154 | The acceptable values for this parameter are: 155 | 156 | - AzureCloud 157 | - AzureChinaCloud 158 | - AzureUSGovernment 159 | - AzureGermanyCloud 160 | 161 | Example: 162 | 163 | ```PowerShell 164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud" 165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud" 166 | ``` 167 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 7 | [string] $azureEnvironmentName 8 | ) 9 | 10 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) { 11 | Install-Module "AzureAD" -Scope CurrentUser 12 | } 13 | Import-Module AzureAD 14 | $ErrorActionPreference = "Stop" 15 | 16 | Function Cleanup 17 | { 18 | if (!$azureEnvironmentName) 19 | { 20 | $azureEnvironmentName = "AzureCloud" 21 | } 22 | 23 | <# 24 | .Description 25 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 26 | #> 27 | 28 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 29 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 30 | 31 | # Login to Azure PowerShell (interactive if credentials are not already provided: 32 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 33 | if (!$Credential -and $TenantId) 34 | { 35 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 36 | } 37 | else 38 | { 39 | if (!$TenantId) 40 | { 41 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 42 | } 43 | else 44 | { 45 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 46 | } 47 | } 48 | 49 | if (!$tenantId) 50 | { 51 | $tenantId = $creds.Tenant.Id 52 | } 53 | $tenant = Get-AzureADTenantDetail 54 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name 55 | 56 | # Removes the applications 57 | Write-Host "Cleaning-up applications from tenant '$tenantName'" 58 | 59 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed" 60 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 61 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" 62 | if ($apps) 63 | { 64 | Remove-AzureADApplication -ObjectId $apps.ObjectId 65 | } 66 | 67 | foreach ($app in $apps) 68 | { 69 | Remove-AzureADApplication -ObjectId $app.ObjectId 70 | Write-Host "Removed Console-Interactive-MultiTarget-v2.." 71 | } 72 | # also remove service principals of this app 73 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 74 | 75 | } 76 | 77 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/Configure.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 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 | Before running this script you need to install the AzureAD cmdlets as an administrator. 15 | For this: 16 | 1) Run Powershell as an administrator 17 | 2) in the PowerShell window, type: Install-Module AzureAD 18 | 19 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. 20 | #> 21 | 22 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure 23 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is 24 | # described in $permissionType 25 | Function AddResourcePermission($requiredAccess, ` 26 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) 27 | { 28 | foreach($permission in $requiredAccesses.Trim().Split("|")) 29 | { 30 | foreach($exposedPermission in $exposedPermissions) 31 | { 32 | if ($exposedPermission.Value -eq $permission) 33 | { 34 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 35 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions 36 | $resourceAccess.Id = $exposedPermission.Id # Read directory data 37 | $requiredAccess.ResourceAccess.Add($resourceAccess) 38 | } 39 | } 40 | } 41 | } 42 | 43 | # 44 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" 45 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell 46 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) 47 | { 48 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique) 49 | if ($servicePrincipal) 50 | { 51 | $sp = $servicePrincipal 52 | } 53 | else 54 | { 55 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" 56 | } 57 | $appid = $sp.AppId 58 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 59 | $requiredAccess.ResourceAppId = $appid 60 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 61 | 62 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: 63 | if ($requiredDelegatedPermissions) 64 | { 65 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 66 | } 67 | 68 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application 69 | if ($requiredApplicationPermissions) 70 | { 71 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 72 | } 73 | return $requiredAccess 74 | } 75 | 76 | 77 | Function UpdateLine([string] $line, [string] $value) 78 | { 79 | $index = $line.IndexOf('=') 80 | $delimiter = ';' 81 | if ($index -eq -1) 82 | { 83 | $index = $line.IndexOf(':') 84 | $delimiter = ',' 85 | } 86 | if ($index -ige 0) 87 | { 88 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter 89 | } 90 | return $line 91 | } 92 | 93 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary) 94 | { 95 | $lines = Get-Content $configFilePath 96 | $index = 0 97 | while($index -lt $lines.Length) 98 | { 99 | $line = $lines[$index] 100 | foreach($key in $dictionary.Keys) 101 | { 102 | if ($line.Contains($key)) 103 | { 104 | $lines[$index] = UpdateLine $line $dictionary[$key] 105 | } 106 | } 107 | $index++ 108 | } 109 | 110 | Set-Content -Path $configFilePath -Value $lines -Force 111 | } 112 | 113 | Set-Content -Value "" -Path createdApps.html 114 | Add-Content -Value "" -Path createdApps.html 115 | 116 | $ErrorActionPreference = "Stop" 117 | 118 | Function ConfigureApplications 119 | { 120 | <#.Description 121 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the 122 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) 123 | so that they are consistent with the Applications parameters 124 | #> 125 | $commonendpoint = "common" 126 | 127 | if (!$azureEnvironmentName) 128 | { 129 | $azureEnvironmentName = "AzureCloud" 130 | } 131 | 132 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 133 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 134 | 135 | # Login to Azure PowerShell (interactive if credentials are not already provided: 136 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 137 | if (!$Credential -and $TenantId) 138 | { 139 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 140 | } 141 | else 142 | { 143 | if (!$TenantId) 144 | { 145 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 146 | } 147 | else 148 | { 149 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 150 | } 151 | } 152 | 153 | if (!$tenantId) 154 | { 155 | $tenantId = $creds.Tenant.Id 156 | } 157 | 158 | 159 | 160 | $tenant = Get-AzureADTenantDetail 161 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name 162 | 163 | # Get the user running the script to add the user as the app owner 164 | $user = Get-AzureADUser -ObjectId $creds.Account.Id 165 | 166 | # Create the client AAD application 167 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)" 168 | # create the application 169 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" ` 170 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" ` 171 | -AvailableToOtherTenants $True ` 172 | -PublicClient $True 173 | 174 | # create the service principal of the newly created application 175 | $currentAppId = $clientAadApplication.AppId 176 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} 177 | 178 | # add the user running the script as an app owner if needed 179 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId 180 | if ($owner -eq $null) 181 | { 182 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId 183 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'" 184 | } 185 | 186 | 187 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)" 188 | 189 | # URL of the AAD application in the Azure portal 190 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 191 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 192 | Add-Content -Value "" -Path createdApps.html 193 | 194 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 195 | 196 | # Add Required Resources Access (from 'client' to 'Microsoft Graph') 197 | Write-Host "Getting access from 'client' to 'Microsoft Graph'" 198 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` 199 | -requiredDelegatedPermissions "User.Read" ` 200 | 201 | $requiredResourcesAccess.Add($requiredPermissions) 202 | 203 | 204 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess 205 | Write-Host "Granted permissions." 206 | 207 | # Update config file for 'client' 208 | $configFile = $pwd.Path + "\..\Console-Interactive-CustomWebUI\appsettings.json" 209 | Write-Host "Updating the sample code ($configFile)" 210 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId }; 211 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary 212 | 213 | Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
client$currentAppIdConsole-Interactive-MultiTarget-v2
" -Path createdApps.html 214 | } 215 | 216 | # Pre-requisites 217 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { 218 | Install-Module "AzureAD" -Scope CurrentUser 219 | } 220 | 221 | Import-Module AzureAD 222 | 223 | # Run interactively (will ask you for the tenant ID) 224 | ConfigureApplications -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API with custom web ui.", 4 | "Level": 300, 5 | "Client": ".NET Desktop (Console)", 6 | "Service": "Microsoft Graph", 7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial", 8 | "Endpoint": "AAD v2.0" 9 | }, 10 | 11 | /* 12 | This section describes the Azure AD Applications to configure, and their dependencies 13 | */ 14 | "AADApps": [ 15 | { 16 | "Id": "client", 17 | "Name": "Console-Interactive-MultiTarget-v2", 18 | "Kind": "Desktop", 19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost", 20 | "RequiredResourcesAccess": [ 21 | { 22 | "Resource": "Microsoft Graph", 23 | "DelegatedPermissions": [ "User.Read" ] 24 | } 25 | ] 26 | } 27 | ], 28 | 29 | /* 30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 31 | are created in Azure AD. 32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 34 | */ 35 | "CodeConfiguration": [ 36 | { 37 | "App": "client", 38 | "SettingKind": "JSon", 39 | "SettingFile": "\\..\\Console-Interactive-CustomWebUI\\appsettings.json", 40 | "Mappings": [ 41 | { 42 | "key": "ClientId", 43 | "value": ".AppId" 44 | }, 45 | { 46 | "key": "TenantId", 47 | "value": "$tenantId" 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-Core.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console-Interactive-CustomWebUI", "Console-Interactive-CustomWebUI\Console-Interactive-CustomWebUI.csproj", "{509D5688-3BD1-4650-8A43-3CEF5B64198E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {42EE218E-9522-4DBA-9DFE-2EC0FB0CD3D5} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/Console-Interactive-CustomWebUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Console_Interactive_CustomWebUI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/CustomWebBrowser/CustomBrowserWebUi.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client.Extensibility; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Web; 13 | 14 | namespace Console_Interactive_CustomWebUI.CustomWebBrowser 15 | { 16 | internal class CustomBrowserWebUi : ICustomWebUi 17 | { 18 | //Success authentication html block 19 | private const string CloseWindowSuccessHtml = @" 20 | Authentication Complete 21 | 22 |
23 |

Custom Web UI

24 |
25 |
26 |

Authentication complete

27 |
You can return to the application. Feel free to close this browser tab.
28 |
29 | 30 | 31 | "; 32 | 33 | //Failure authentication html block 34 | private const string CloseWindowFailureHtml = @" 35 | Authentication Failed 36 | 37 |
38 |

Custom Web UI

39 |
40 |
41 |

Authentication failed

42 |
Error details: error {0} error_description: {1}
43 |
44 |
You can return to the application. Feel free to close this browser tab.
45 |
46 | 47 | 48 | "; 49 | 50 | public async Task AcquireAuthorizationCodeAsync( 51 | Uri authorizationUri, 52 | Uri redirectUri, 53 | CancellationToken cancellationToken) 54 | { 55 | if (!redirectUri.IsLoopback) 56 | { 57 | throw new ArgumentException("Only loopback redirect uri is supported with this WebUI. Configure http://localhost or http://localhost:port during app registration. "); 58 | } 59 | 60 | Uri result = await InterceptAuthorizationUriAsync(authorizationUri,redirectUri,cancellationToken) 61 | .ConfigureAwait(true); 62 | 63 | return result; 64 | } 65 | 66 | public static string FindFreeLocalhostRedirectUri() 67 | { 68 | TcpListener listner = new TcpListener(IPAddress.Loopback, 0); 69 | try 70 | { 71 | listner.Start(); 72 | int port = ((IPEndPoint)listner.LocalEndpoint).Port; 73 | return "http://localhost:" + port; 74 | } 75 | finally 76 | { 77 | listner?.Stop(); 78 | } 79 | } 80 | 81 | private static void OpenBrowser(string url) 82 | { 83 | try 84 | { 85 | ProcessStartInfo psi = new ProcessStartInfo 86 | { 87 | FileName = url, 88 | UseShellExecute = true 89 | }; 90 | Process.Start(psi); 91 | } 92 | catch 93 | { 94 | // hack because of this: https://github.com/dotnet/corefx/issues/10361 95 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 96 | { 97 | url = url.Replace("&", "^&"); 98 | Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true }); 99 | } 100 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 101 | { 102 | Process.Start("xdg-open", url); 103 | } 104 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 105 | { 106 | Process.Start("open", url); 107 | } 108 | else 109 | { 110 | throw new PlatformNotSupportedException(RuntimeInformation.OSDescription); 111 | } 112 | } 113 | } 114 | 115 | /// 116 | /// Opens a new tab on the OS default browser and navigates to the authorization URI, while listening to its response. 117 | /// Then, displays an HTML block based on the authorization response. 118 | /// 119 | private async Task InterceptAuthorizationUriAsync( 120 | Uri authorizationUri, 121 | Uri redirectUri, 122 | CancellationToken cancellationToken) 123 | { 124 | // Opens a browser sending the authorization request 125 | OpenBrowser(authorizationUri.ToString()); 126 | 127 | // Listens to the localhost socket that opened the request 128 | using (var listener = new SingleMessageTcpListener(redirectUri.Port)) 129 | { 130 | Uri authCodeUri = null; 131 | await listener.ListenToSingleRequestAndRespondAsync( 132 | (uri) => 133 | { 134 | Trace.WriteLine("Intercepted an auth code url: " + uri.ToString()); 135 | authCodeUri = uri; 136 | 137 | // Displays the success or failure HTML block based on the authorization response 138 | return GetMessageToShowInBroswerAfterAuth(uri); 139 | }, 140 | cancellationToken) 141 | .ConfigureAwait(false); 142 | 143 | return authCodeUri; 144 | } 145 | } 146 | 147 | // Parses the authorization response and displays the success or failure HTML block accordingly 148 | private static string GetMessageToShowInBroswerAfterAuth(Uri uri) 149 | { 150 | // Parse the uri to understand if an error was returned. This is done just to show the user a nice error message in the browser. 151 | var authCodeQueryKeyValue = HttpUtility.ParseQueryString(uri.Query); 152 | 153 | string errorString = authCodeQueryKeyValue.Get("error"); 154 | if (!string.IsNullOrEmpty(errorString)) 155 | { 156 | return string.Format( 157 | CultureInfo.InvariantCulture, 158 | CloseWindowFailureHtml, 159 | errorString, 160 | authCodeQueryKeyValue.Get("error_description")); 161 | } 162 | 163 | return CloseWindowSuccessHtml; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/CustomWebBrowser/SingleMessageTcpListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Console_Interactive_CustomWebUI.CustomWebBrowser 11 | { 12 | internal class SingleMessageTcpListener : IDisposable 13 | { 14 | private readonly int _port; 15 | private readonly TcpListener _tcpListener; 16 | 17 | public SingleMessageTcpListener(int port) 18 | { 19 | if (port < 1 || port == 80) 20 | { 21 | throw new ArgumentOutOfRangeException("Expected a valid port number, > 0, not 80"); 22 | } 23 | 24 | _port = port; 25 | _tcpListener = new TcpListener(IPAddress.Loopback, _port); 26 | 27 | 28 | } 29 | 30 | public async Task ListenToSingleRequestAndRespondAsync(Func responseProducer, CancellationToken cancellationToken) 31 | { 32 | cancellationToken.Register(() => _tcpListener.Stop()); 33 | _tcpListener.Start(); 34 | 35 | TcpClient tcpClient = null; 36 | try 37 | { 38 | tcpClient = await AcceptTcpClientAsync(cancellationToken) 39 | .ConfigureAwait(false); 40 | 41 | await ExtractUriAndRespondAsync(tcpClient, responseProducer, cancellationToken).ConfigureAwait(false); 42 | 43 | } 44 | finally 45 | { 46 | tcpClient?.Close(); 47 | } 48 | } 49 | 50 | /// 51 | /// AcceptTcpClientAsync does not natively support cancellation, so use this wrapper. Make sure 52 | /// the cancellation token is registered to stop the listener. 53 | /// 54 | /// See https://stackoverflow.com/questions/19220957/tcplistener-how-to-stop-listening-while-awaiting-accepttcpclientasync 55 | private async Task AcceptTcpClientAsync(CancellationToken token) 56 | { 57 | try 58 | { 59 | return await _tcpListener.AcceptTcpClientAsync().ConfigureAwait(false); 60 | } 61 | catch (Exception ex) when (token.IsCancellationRequested) 62 | { 63 | throw new OperationCanceledException("Cancellation was requested while awaiting TCP client connection.", ex); 64 | } 65 | } 66 | 67 | private async Task ExtractUriAndRespondAsync( 68 | TcpClient tcpClient, 69 | Func responseProducer, 70 | CancellationToken cancellationToken) 71 | { 72 | cancellationToken.ThrowIfCancellationRequested(); 73 | 74 | string httpRequest = await GetTcpResponseAsync(tcpClient, cancellationToken).ConfigureAwait(false); 75 | Uri uri = ExtractUriFromHttpRequest(httpRequest); 76 | 77 | // write an "OK, please close the browser message" 78 | await WriteResponseAsync(responseProducer(uri), tcpClient.GetStream(), cancellationToken) 79 | .ConfigureAwait(false); 80 | } 81 | 82 | #pragma warning disable CS1570 // XML comment has badly formed XML 83 | /// 84 | /// Example TCP response: 85 | /// 86 | /// {GET /?code=OAQABAAIAAAC5una0EUFgTIF8ElaxtWjTl5wse5YHycjcaO_qJukUUexKz660btJtJSiQKz1h4b5DalmXspKis-bS6Inu8lNs4CpoE4FITrLv00Mr3MEYEQzgrn6JiNoIwDFSl4HBzHG8Kjd4Ho65QGUMVNyTjhWyQDf_12E8Gw9sll_sbOU51FIreZlVuvsqIWBMIJ8mfmExZBSckofV6LbcKJTeEZKaqjC09x3k1dpsCNJAtYTQIus5g1DyhAW8viDpWDpQJlT55_0W4rrNKY3CSD5AhKd3Ng4_ePPd7iC6qObfmMBlCcldX688vR2IghV0GoA0qNalzwqP7lov-yf38uVZ3ir6VlDNpbzCoV-drw0zhlMKgSq6LXT7QQYmuA4RVy_7TE9gjQpW-P0_ZXUHirpgdsblaa3JUq4cXpbMU8YCLQm7I2L0oCkBTupYXKLoM2gHSYPJ5HChhj1x0pWXRzXdqbx_TPTujBLsAo4Skr_XiLQ4QPJZpkscmXezpPa5Z87gDenUBRBI9ppROhOksekMbvPataF0qBaM38QzcnzeOCFyih1OjIKsq3GeryChrEtfY9CL9lBZ6alIIQB4thD__Tc24OUmr04hX34PjMyt1Z9Qvr76Pw0r7A52JvqQLWupx8bqok6AyCwqUGfLCPjwylSLA7NYD7vScAbfkOOszfoCC3ff14Dqm3IAB1tUJfCZoab61c6Mozls74c2Ujr3roHw4NdPuo-re5fbpSw5RVu8MffWYwXrO3GdmgcvIMkli2uperucLldNVIp6Pc3MatMYSBeAikuhtaZiZAhhl3uQxzoMhU-MO9WXuG2oIkqSvKjghxi1NUhfTK4-du7I5h1r0lFh9b3h8kvE1WBhAIxLdSAA&state=b380f309-7d24-4793-b938-e4a512b2c7f6&session_state=a442c3cd-a25e-4b88-8b33-36d194ba11b2 HTTP/1.1 87 | /// Host: localhost:9001 88 | /// Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,ro;q=0.7,fr;q=0.6 89 | /// Connection: keep-alive 90 | /// Upgrade-Insecure-Requests: 1 91 | /// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 92 | /// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 93 | /// Accept-Encoding: gzip, deflate, br 94 | /// 95 | /// http://localhost:9001/?code=foo&session_state=bar 96 | private Uri ExtractUriFromHttpRequest(string httpRequest) 97 | #pragma warning restore CS1570 // XML comment has badly formed XML 98 | { 99 | string regexp = @"GET \/\?(.*) HTTP"; 100 | string getQuery = null; 101 | Regex r1 = new Regex(regexp); 102 | Match match = r1.Match(httpRequest); 103 | if (!match.Success) 104 | { 105 | throw new InvalidOperationException("Not a GET query"); 106 | } 107 | 108 | getQuery = match.Groups[1].Value; 109 | UriBuilder uriBuilder = new UriBuilder(); 110 | uriBuilder.Query = getQuery; 111 | uriBuilder.Port = _port; 112 | 113 | return uriBuilder.Uri; 114 | } 115 | 116 | private static async Task GetTcpResponseAsync(TcpClient client, CancellationToken cancellationToken) 117 | { 118 | NetworkStream networkStream = client.GetStream(); 119 | 120 | byte[] readBuffer = new byte[1024]; 121 | StringBuilder stringBuilder = new StringBuilder(); 122 | int numberOfBytesRead = 0; 123 | 124 | // Incoming message may be larger than the buffer size. 125 | do 126 | { 127 | numberOfBytesRead = await networkStream.ReadAsync(readBuffer, 0, readBuffer.Length, cancellationToken) 128 | .ConfigureAwait(false); 129 | 130 | string s = Encoding.ASCII.GetString(readBuffer, 0, numberOfBytesRead); 131 | stringBuilder.Append(s); 132 | 133 | } 134 | while (networkStream.DataAvailable); 135 | 136 | return stringBuilder.ToString(); 137 | } 138 | 139 | private async Task WriteResponseAsync(string message, NetworkStream stream, CancellationToken cancellationToken) 140 | { 141 | string fullResponse = $"HTTP/1.1 200 OK\r\n\r\n{message}"; 142 | var response = Encoding.ASCII.GetBytes(fullResponse); 143 | await stream.WriteAsync(response, 0, response.Length, cancellationToken).ConfigureAwait(false); 144 | await stream.FlushAsync(cancellationToken).ConfigureAwait(false); 145 | } 146 | 147 | public void Dispose() 148 | { 149 | _tcpListener?.Stop(); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/Program.cs: -------------------------------------------------------------------------------- 1 | using Console_Interactive_CustomWebUI.CustomWebBrowser; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Graph; 4 | using Microsoft.Identity.Client; 5 | using Microsoft.Identity.Client.Extensibility; 6 | using System; 7 | using System.Linq; 8 | using System.Net.Http.Headers; 9 | using System.Threading.Tasks; 10 | 11 | namespace Console_Interactive_CustomWebUI 12 | { 13 | class Program 14 | { 15 | private static PublicClientApplicationOptions appConfiguration = null; 16 | private static IConfiguration configuration; 17 | private static string graphURL; 18 | 19 | // The MSAL Public client app 20 | private static IPublicClientApplication application; 21 | 22 | // Since the browser is started via Process.Start, there is no control over it, 23 | // So it is recommended to configure a timeout 24 | private const int TimeoutWaitingForBrowserMs = 30 * 1000; //30 seconds 25 | 26 | static async Task Main(string[] args) 27 | { 28 | // Using appsettings.json as our configuration settings 29 | var builder = new ConfigurationBuilder() 30 | .SetBasePath(System.IO.Directory.GetCurrentDirectory()) 31 | .AddJsonFile("appsettings.json"); 32 | 33 | configuration = builder.Build(); 34 | 35 | appConfiguration = configuration 36 | .Get(); 37 | 38 | string[] scopes = new[] { "user.read" }; 39 | 40 | graphURL = configuration.GetValue("GraphApiUrl"); 41 | 42 | // Sign-in user using MSAL and obtain an access token for MS Graph 43 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes); 44 | 45 | // Call the /me endpoint of MS Graph 46 | await CallMSGraph(graphClient); 47 | 48 | Console.ReadKey(); 49 | } 50 | 51 | /// 52 | /// Sign in user using MSAL and obtain a token for MS Graph 53 | /// 54 | /// 55 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes) 56 | { 57 | GraphServiceClient graphClient = new GraphServiceClient(graphURL, 58 | new DelegateAuthenticationProvider(async (requestMessage) => 59 | { 60 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes)); 61 | })); 62 | 63 | return await Task.FromResult(graphClient); 64 | } 65 | 66 | /// 67 | /// Signs in the user using the device code flow and obtains an Access token for MS Graph 68 | /// 69 | /// 70 | /// 71 | /// 72 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes) 73 | { 74 | // build the AAd authority Url 75 | string authority = string.Concat(configuration.Instance, configuration.TenantId); 76 | 77 | // Initialize the MSAL library by building a public client application 78 | application = PublicClientApplicationBuilder.Create(configuration.ClientId) 79 | .WithAuthority(authority) 80 | .WithRedirectUri(CustomBrowserWebUi.FindFreeLocalhostRedirectUri()) // required for CustomBrowserWebUi 81 | .Build(); 82 | 83 | 84 | AuthenticationResult result; 85 | 86 | try 87 | { 88 | var accounts = await application.GetAccountsAsync(); 89 | 90 | // Try to acquire an access token from the cache, if UI interaction is required, MsalUiRequiredException will be thrown. 91 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync(); 92 | } 93 | catch (MsalUiRequiredException) 94 | { 95 | // Acquiring an access token interactively using custom web UI. 96 | result = await application.AcquireTokenInteractive(scopes) 97 | .WithCustomWebUi(new CustomBrowserWebUi()) //Using our custom web ui 98 | .ExecuteAsync(); 99 | } 100 | 101 | return result.AccessToken; 102 | } 103 | 104 | /// 105 | /// Call MS Graph and print results 106 | /// 107 | /// 108 | /// 109 | private static async Task CallMSGraph(GraphServiceClient graphClient) 110 | { 111 | var me = await graphClient.Me.Request().GetAsync(); 112 | 113 | // Printing the results 114 | Console.Write(Environment.NewLine); 115 | Console.WriteLine("-------- Data from call to MS Graph --------"); 116 | Console.Write(Environment.NewLine); 117 | Console.WriteLine($"Id: {me.Id}"); 118 | Console.WriteLine($"Display Name: {me.DisplayName}"); 119 | Console.WriteLine($"Email: {me.Mail}"); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Instance": "https://login.microsoftonline.com/", 3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]", 4 | "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]", 5 | "RedirectUri": "http://localhost", 6 | "GraphApiUrl": "https://graph.microsoft.com/beta/" 7 | } 8 | -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/failureMessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/failureMessage.png -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/successMessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/successMessage.png -------------------------------------------------------------------------------- /3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /4-DeviceCodeFlow/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory 8 | 1. In PowerShell run: 9 | ```PowerShell 10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 11 | ``` 12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below) 13 | ```PowerShell 14 | cd .\AppCreationScripts\ 15 | .\Configure.ps1 16 | ``` 17 | 1. Open the Visual Studio solution and click start 18 | 19 | ### More details 20 | 21 | The following paragraphs: 22 | 23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 24 | - Explain the [pre-requisites](#pre-requisites) 25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 26 | - [Interactively](#option-1-interactive) to create the app in your home tenant 27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds) 31 | 32 | ## Goal of the scripts 33 | 34 | ### Presentation of the scripts 35 | 36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra 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. 37 | 38 | These scripts are: 39 | 40 | - `Configure.ps1` which: 41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets), 42 | - changes the configuration files in the C# and JavaScript projects. 43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created: 44 | - the identifier of the application 45 | - the AppId of the application 46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 47 | 48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra 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). 49 | 50 | ### Usage pattern for tests and DevOps scenarios 51 | 52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra 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. 53 | 54 | ## How to use the app creation scripts? 55 | 56 | ### Pre-requisites 57 | 58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 59 | 2. Navigate to the root directory of the project. 60 | 3. 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: 61 | ```PowerShell 62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 63 | ``` 64 | ### (Optionally) install AzureAD PowerShell modules 65 | The scripts install the required PowerShell module (AzureAD) 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: 66 | 67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 68 | 69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 70 | 2. Type: 71 | ```PowerShell 72 | Install-Module AzureAD 73 | ``` 74 | 75 | or if you cannot be administrator on your machine, run: 76 | ```PowerShell 77 | Install-Module AzureAD -Scope CurrentUser 78 | ``` 79 | 80 | ### Run the script and start running 81 | 82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 83 | ```PowerShell 84 | cd AppCreationScripts 85 | ``` 86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 88 | 8. select **Start** for the projects 89 | 90 | You're done. this just works! 91 | 92 | ### Four ways to run the script 93 | 94 | We advise four ways of running the script: 95 | 96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 98 | - 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, 99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. 100 | 101 | Here are the details on how to do this. 102 | 103 | #### Option 1 (interactive) 104 | 105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 107 | 108 | 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. 109 | 110 | #### Option 2 (non-interactive) 111 | 112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window 113 | 114 | ```PowerShell 115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 117 | . .\Cleanup.ps1 -Credential $mycreds 118 | . .\Configure.ps1 -Credential $mycreds 119 | ``` 120 | 121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 122 | 123 | #### Option 3 (Interactive, but create apps in a specified tenant) 124 | 125 | if you want to create the apps in a particular tenant, you can use the following option: 126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com) 127 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 128 | - Find the "Active Directory" object in this tenant 129 | - Go to **Properties** and copy the content of the **Directory Id** property 130 | - Then use the full syntax to run the scripts: 131 | 132 | ```PowerShell 133 | $tenantId = "yourTenantIdGuid" 134 | . .\Cleanup.ps1 -TenantId $tenantId 135 | . .\Configure.ps1 -TenantId $tenantId 136 | ``` 137 | 138 | #### Option 4 (non-interactive, and create apps in a specified tenant) 139 | 140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: 141 | 142 | ```PowerShell 143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 145 | $tenantId = "yourTenantIdGuid" 146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 148 | ``` 149 | 150 | ### Running the script on Azure Sovereign clouds 151 | 152 | 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`. 153 | 154 | The acceptable values for this parameter are: 155 | 156 | - AzureCloud 157 | - AzureChinaCloud 158 | - AzureUSGovernment 159 | - AzureGermanyCloud 160 | 161 | Example: 162 | 163 | ```PowerShell 164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud" 165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud" 166 | ``` 167 | -------------------------------------------------------------------------------- /4-DeviceCodeFlow/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 7 | [string] $azureEnvironmentName 8 | ) 9 | 10 | #Requires -Modules AzureAD 11 | 12 | 13 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) { 14 | Install-Module "AzureAD" -Scope CurrentUser 15 | } 16 | Import-Module AzureAD 17 | $ErrorActionPreference = "Stop" 18 | 19 | Function Cleanup 20 | { 21 | if (!$azureEnvironmentName) 22 | { 23 | $azureEnvironmentName = "AzureCloud" 24 | } 25 | 26 | <# 27 | .Description 28 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 29 | #> 30 | 31 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 32 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 33 | 34 | # Login to Azure PowerShell (interactive if credentials are not already provided: 35 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 36 | if (!$Credential -and $TenantId) 37 | { 38 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 39 | } 40 | else 41 | { 42 | if (!$TenantId) 43 | { 44 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 45 | } 46 | else 47 | { 48 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 49 | } 50 | } 51 | 52 | if (!$tenantId) 53 | { 54 | $tenantId = $creds.Tenant.Id 55 | } 56 | $tenant = Get-AzureADTenantDetail 57 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name 58 | 59 | # Removes the applications 60 | Write-Host "Cleaning-up applications from tenant '$tenantName'" 61 | 62 | Write-Host "Removing 'client' (Console-DeviceCodeFlow-MultiTarget-v2) if needed" 63 | Get-AzureADApplication -Filter "DisplayName eq 'Console-DeviceCodeFlow-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 64 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-DeviceCodeFlow-MultiTarget-v2'" 65 | if ($apps) 66 | { 67 | Remove-AzureADApplication -ObjectId $apps.ObjectId 68 | } 69 | 70 | foreach ($app in $apps) 71 | { 72 | Remove-AzureADApplication -ObjectId $app.ObjectId 73 | Write-Host "Removed Console-DeviceCodeFlow-MultiTarget-v2.." 74 | } 75 | # also remove service principals of this app 76 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-DeviceCodeFlow-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 77 | 78 | } 79 | 80 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /4-DeviceCodeFlow/AppCreationScripts/Configure.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [PSCredential] $Credential, 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 (it defaults to AzureCloud)')] 7 | [string] $azureEnvironmentName 8 | ) 9 | 10 | #Requires -Modules AzureAD 11 | 12 | <# 13 | This script creates the Azure AD applications needed for this sample and updates the configuration files 14 | for the visual Studio projects from the data in the Azure AD applications. 15 | 16 | Before running this script you need to install the AzureAD cmdlets as an administrator. 17 | For this: 18 | 1) Run Powershell as an administrator 19 | 2) in the PowerShell window, type: Install-Module AzureAD 20 | 21 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script. 22 | #> 23 | 24 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure 25 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is 26 | # described in $permissionType 27 | Function AddResourcePermission($requiredAccess, ` 28 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) 29 | { 30 | foreach($permission in $requiredAccesses.Trim().Split("|")) 31 | { 32 | foreach($exposedPermission in $exposedPermissions) 33 | { 34 | if ($exposedPermission.Value -eq $permission) 35 | { 36 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess 37 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions 38 | $resourceAccess.Id = $exposedPermission.Id # Read directory data 39 | $requiredAccess.ResourceAccess.Add($resourceAccess) 40 | } 41 | } 42 | } 43 | } 44 | 45 | # 46 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" 47 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell 48 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) 49 | { 50 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique) 51 | if ($servicePrincipal) 52 | { 53 | $sp = $servicePrincipal 54 | } 55 | else 56 | { 57 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" 58 | } 59 | $appid = $sp.AppId 60 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 61 | $requiredAccess.ResourceAppId = $appid 62 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 63 | 64 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: 65 | if ($requiredDelegatedPermissions) 66 | { 67 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 68 | } 69 | 70 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application 71 | if ($requiredApplicationPermissions) 72 | { 73 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 74 | } 75 | return $requiredAccess 76 | } 77 | 78 | 79 | Function UpdateLine([string] $line, [string] $value) 80 | { 81 | $index = $line.IndexOf('=') 82 | $delimiter = ';' 83 | if ($index -eq -1) 84 | { 85 | $index = $line.IndexOf(':') 86 | $delimiter = ',' 87 | } 88 | if ($index -ige 0) 89 | { 90 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter 91 | } 92 | return $line 93 | } 94 | 95 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary) 96 | { 97 | $lines = Get-Content $configFilePath 98 | $index = 0 99 | while($index -lt $lines.Length) 100 | { 101 | $line = $lines[$index] 102 | foreach($key in $dictionary.Keys) 103 | { 104 | if ($line.Contains($key)) 105 | { 106 | $lines[$index] = UpdateLine $line $dictionary[$key] 107 | } 108 | } 109 | $index++ 110 | } 111 | 112 | Set-Content -Path $configFilePath -Value $lines -Force 113 | } 114 | 115 | Set-Content -Value "" -Path createdApps.html 116 | Add-Content -Value "" -Path createdApps.html 117 | 118 | $ErrorActionPreference = "Stop" 119 | 120 | Function ConfigureApplications 121 | { 122 | <#.Description 123 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the 124 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) 125 | so that they are consistent with the Applications parameters 126 | #> 127 | $commonendpoint = "common" 128 | 129 | if (!$azureEnvironmentName) 130 | { 131 | $azureEnvironmentName = "AzureCloud" 132 | } 133 | 134 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 135 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 136 | 137 | # Login to Azure PowerShell (interactive if credentials are not already provided: 138 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 139 | if (!$Credential -and $TenantId) 140 | { 141 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName 142 | } 143 | else 144 | { 145 | if (!$TenantId) 146 | { 147 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 148 | } 149 | else 150 | { 151 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName 152 | } 153 | } 154 | 155 | if (!$tenantId) 156 | { 157 | $tenantId = $creds.Tenant.Id 158 | } 159 | 160 | 161 | 162 | $tenant = Get-AzureADTenantDetail 163 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name 164 | 165 | # Get the user running the script to add the user as the app owner 166 | $user = Get-AzureADUser -ObjectId $creds.Account.Id 167 | 168 | # Create the client AAD application 169 | Write-Host "Creating the AAD application (Console-DeviceCodeFlow-MultiTarget-v2)" 170 | # create the application 171 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-DeviceCodeFlow-MultiTarget-v2" ` 172 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient" ` 173 | -PublicClient $True 174 | 175 | # create the service principal of the newly created application 176 | $currentAppId = $clientAadApplication.AppId 177 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} 178 | 179 | # add the user running the script as an app owner if needed 180 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId 181 | if ($owner -eq $null) 182 | { 183 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId 184 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'" 185 | } 186 | 187 | 188 | Write-Host "Done creating the client application (Console-DeviceCodeFlow-MultiTarget-v2)" 189 | 190 | # URL of the AAD application in the Azure portal 191 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 192 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 193 | Add-Content -Value "" -Path createdApps.html 194 | 195 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 196 | 197 | # Add Required Resources Access (from 'client' to 'Microsoft Graph') 198 | Write-Host "Getting access from 'client' to 'Microsoft Graph'" 199 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` 200 | -requiredDelegatedPermissions "User.Read" ` 201 | 202 | $requiredResourcesAccess.Add($requiredPermissions) 203 | 204 | 205 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess 206 | Write-Host "Granted permissions." 207 | 208 | # Update config file for 'client' 209 | $configFile = $pwd.Path + "\..\Console-DeviceCodeFlow-v2\appsettings.json" 210 | Write-Host "Updating the sample code ($configFile)" 211 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId }; 212 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary 213 | 214 | Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
client$currentAppIdConsole-DeviceCodeFlow-MultiTarget-v2
" -Path createdApps.html 215 | } 216 | 217 | # Pre-requisites 218 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { 219 | Install-Module "AzureAD" -Scope CurrentUser 220 | } 221 | 222 | Import-Module AzureAD 223 | 224 | # Run interactively (will ask you for the tenant ID) 225 | ConfigureApplications -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /4-DeviceCodeFlow/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "Sign-in a user with the Microsoft identity platform using the device code flow and call Microsoft Graph on the user's behalf.", 4 | "Level": 100, 5 | "Client": ".NET Desktop (Console)", 6 | "Service": "Microsoft Graph", 7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial", 8 | "Endpoint": "AAD v2.0" 9 | }, 10 | 11 | /* 12 | This section describes the Azure AD Applications to configure, and their dependencies 13 | */ 14 | "AADApps": [ 15 | { 16 | "Id": "client", 17 | "Name": "Console-DeviceCodeFlow-MultiTarget-v2", 18 | "Kind": "Desktop", 19 | "UsesROPCOrIWA": true, 20 | "Audience": "AzureADMyOrg", 21 | "RequiredResourcesAccess": [ 22 | { 23 | "Resource": "Microsoft Graph", 24 | "DelegatedPermissions": [ "User.Read" ] 25 | } 26 | ] 27 | } 28 | ], 29 | 30 | /* 31 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 32 | are created in Azure AD. 33 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 34 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 35 | */ 36 | "CodeConfiguration": [ 37 | { 38 | "App": "client", 39 | "SettingKind": "JSon", 40 | "SettingFile": "\\..\\Console-DeviceCodeFlow-v2\\appsettings.json", 41 | "Mappings": [ 42 | { 43 | "key": "ClientId", 44 | "value": ".AppId" 45 | }, 46 | { 47 | "key": "TenantId", 48 | "value": "$tenantId" 49 | } 50 | ] 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /4-DeviceCodeFlow/Console-DeviceCodeFlow-v2.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30011.22 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console-DeviceCodeFlow-v2", "Console-DeviceCodeFlow-v2\Console-DeviceCodeFlow-v2.csproj", "{23239B0D-FF39-43E7-998D-BCC885D918C2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {04F72127-4CDB-4189-A854-4B2C7FD7D6C3} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /4-DeviceCodeFlow/Console-DeviceCodeFlow-v2/Console-DeviceCodeFlow-v2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1; net472 6 | Console_DeviceCodeFlow_MultiTarget 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /4-DeviceCodeFlow/Console-DeviceCodeFlow-v2/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Graph; 3 | using Microsoft.Identity.Client; 4 | using System; 5 | using System.Linq; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | 9 | namespace Console_DeviceCodeFlow_MultiTarget 10 | { 11 | internal class Program 12 | { 13 | private static PublicClientApplicationOptions appConfiguration = null; 14 | private static IConfiguration configuration; 15 | private static string MSGraphURL; 16 | 17 | // The MSAL Public client app 18 | private static IPublicClientApplication application; 19 | 20 | private static async Task Main(string[] args) 21 | { 22 | // Using appsettings.json to load the configuration settings 23 | var builder = new ConfigurationBuilder() 24 | .SetBasePath(System.IO.Directory.GetCurrentDirectory()) 25 | .AddJsonFile("appsettings.json"); 26 | 27 | configuration = builder.Build(); 28 | 29 | appConfiguration = configuration.Get(); 30 | 31 | // We intend to obtain a token for Graph for the following scopes (permissions) 32 | string[] scopes = new[] { "user.read" }; 33 | 34 | MSGraphURL = configuration.GetValue("GraphApiUrl"); 35 | 36 | // Sign-in user using MSAL and obtain an access token for MS Graph 37 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes); 38 | 39 | // Call the /me endpoint of MS Graph 40 | await CallMSGraph(graphClient); 41 | } 42 | 43 | /// 44 | /// Signs in the user using the device code flow and obtains an Access token for MS Graph 45 | /// 46 | /// 47 | /// 48 | /// 49 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes) 50 | { 51 | // build the AAd authority Url 52 | string authority = string.Concat(configuration.Instance, configuration.TenantId); 53 | 54 | // Initialize the MSAL library by building a public client application 55 | application = PublicClientApplicationBuilder.Create(configuration.ClientId) 56 | .WithAuthority(authority) 57 | .WithDefaultRedirectUri() 58 | .Build(); 59 | 60 | 61 | AuthenticationResult result; 62 | 63 | try 64 | { 65 | var accounts = await application.GetAccountsAsync(); 66 | // Try to acquire an access token from the cache. If device code is required, Exception will be thrown. 67 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync(); 68 | } 69 | catch (MsalUiRequiredException) 70 | { 71 | result = await application.AcquireTokenWithDeviceCode(scopes, deviceCodeResult => 72 | { 73 | // This will print the message on the console which tells the user where to go sign-in using 74 | // a separate browser and the code to enter once they sign in. 75 | // The AcquireTokenWithDeviceCode() method will poll the server after firing this 76 | // device code callback to look for the successful login of the user via that browser. 77 | // This background polling (whose interval and timeout data is also provided as fields in the 78 | // deviceCodeCallback class) will occur until: 79 | // * The user has successfully logged in via browser and entered the proper code 80 | // * The timeout specified by the server for the lifetime of this code (typically ~15 minutes) has been reached 81 | // * The developing application calls the Cancel() method on a CancellationToken sent into the method. 82 | // If this occurs, an OperationCanceledException will be thrown (see catch below for more details). 83 | Console.WriteLine(deviceCodeResult.Message); 84 | return Task.FromResult(0); 85 | }).ExecuteAsync(); 86 | } 87 | return result.AccessToken; 88 | } 89 | 90 | /// 91 | /// Sign in user using MSAL and obtain a token for MS Graph 92 | /// 93 | /// 94 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes) 95 | { 96 | GraphServiceClient graphClient = new GraphServiceClient(MSGraphURL, 97 | new DelegateAuthenticationProvider(async (requestMessage) => 98 | { 99 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes)); 100 | })); 101 | 102 | return await Task.FromResult(graphClient); 103 | } 104 | 105 | /// 106 | /// Call MS Graph and print results 107 | /// 108 | /// 109 | /// 110 | private static async Task CallMSGraph(GraphServiceClient graphClient) 111 | { 112 | var me = await graphClient.Me.Request().GetAsync(); 113 | 114 | // Printing the results 115 | Console.Write(Environment.NewLine); 116 | Console.WriteLine("-------- Data from call to MS Graph --------"); 117 | Console.Write(Environment.NewLine); 118 | Console.WriteLine($"Id: {me.Id}"); 119 | Console.WriteLine($"Display Name: {me.DisplayName}"); 120 | Console.WriteLine($"Email: {me.Mail}"); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /4-DeviceCodeFlow/Console-DeviceCodeFlow-v2/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Instance": "https://login.microsoftonline.com/", 3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]", 4 | "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]", 5 | "GraphApiUrl": "https://graph.microsoft.com/v1.0/" 6 | } 7 | -------------------------------------------------------------------------------- /4-DeviceCodeFlow/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/4-DeviceCodeFlow/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - csharp 5 | - powershell 6 | products: 7 | - azure 8 | - microsoft-entra-id 9 | - dotnet 10 | - aspnet 11 | - ms-graph 12 | description: "Learn how to acquire an access token on a console application." 13 | urlFragment: "ms-identity-dotnet-desktop-tutorial" 14 | --- 15 | 16 | ![.NET Core](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/workflows/.NET%20Core/badge.svg) 17 | 18 | # Acquiring an access token using a console application and call APIs with the Microsoft identity platform for developers 19 | 20 | ## About this sample 21 | 22 | A multi-target console application (.Net Core and .Net Framework) that acquires an access token for a protected API on Azure, using Microsoft identity platform for developers. There are steps demonstrating this scenario on Microsoft Entra ID, Azure Active Directory B2C and National Clouds. 23 | 24 | On later steps, you will learn how to enrich the console application with a cross platform token cache and a custom Web UI (for .NET Core only). 25 | 26 | ## Structure of the repository 27 | 28 | This repository contains a progressive tutorial made of the following parts: 29 | 30 | | Sub folder | Description | 31 | | -------------------------------- | -------------------------------- | 32 | | [1. Calling Microsoft Graph](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/1-Calling-MSGraph) | This first part presents how to acquire an access token for Microsoft Graph, on Microsoft Entra ID, Azure B2C and Azure National Clouds. Each scenario is separated on its correspondent sub-folder.| 33 | | [2. Cross platform token cache](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/2-TokenCache) | This step shows how to configure a cross platform token cache (Windows, Linux and MAC) leveraging `Microsoft.Identity.Client.Extensions.Msal` | 34 | | [3. Custom Web UI](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/3-CustomWebUI) | This step shows how to customize the UI on the authorization response for a console application. | 35 | | [4. Device Code flow](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/4-DeviceCodeFlow) | This step shows how to authenticate using device code flow.| 36 | 37 | ## Prerequisites 38 | 39 | - Install .NET Core for Windows by following the instructions at [dot.net/core](https://dot.net/core). 40 | - An Internet connection 41 | - a Microsoft Entra tenant. For more information on how to get a Microsoft Entra tenant, see [How to get a Microsoft Entra tenant](https://azure.microsoft.com/en-us/documentation/articles/active-directory-howto-tenant/) 42 | - A user account in your Microsoft Entra tenant. 43 | 44 | ## Setup 45 | 46 | From your shell or command line: 47 | 48 | ```Shell 49 | git clone https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial.git dotnet-desktop-tutorial 50 | cd dotnet-desktop-tutorial 51 | ``` 52 | 53 | > Given that the name of the sample is pretty long, that it has sub-folders and so are the name of the referenced NuGet packages, you might want to clone it in a folder close to the root of your hard drive, to avoid file size limitations on Windows. 54 | 55 | ## Community Help and Support 56 | 57 | Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. 58 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. 59 | Make sure that your questions or comments are tagged with [`msal` `dotnet`]. 60 | 61 | If you find a bug in the sample, please open an issue on [GitHub Issues](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/issues). 62 | 63 | To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory). 64 | 65 | ## Contributing 66 | 67 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md). 68 | 69 | 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. 70 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | --------------------------------------------------------------------------------