├── .github ├── PULL_REQUEST_TEMPLATE.md ├── bug_report.md └── dependabot.yml ├── .gitignore ├── 1-Call-MSGraph ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ ├── apps.json │ └── sample.json ├── README.md ├── ReadmeFiles │ ├── daemon-with-certificate.svg │ ├── daemon-with-secret.svg │ ├── topology-certificates.png │ └── topology.png ├── daemon-console.sln └── daemon-console │ ├── Program.cs │ ├── appsettings.json │ └── daemon-console.csproj ├── 2-Call-OwnApi ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json ├── README.md ├── ReadmeFiles │ ├── daemon-with-certificate.svg │ ├── daemon-with-secret.svg │ ├── topology-certificates.png │ └── topology.png ├── TodoList-WebApi │ ├── Controllers │ │ └── TodoListController.cs │ ├── Models │ │ └── TodoItem.cs │ ├── Program.cs │ ├── Properties │ │ ├── launchSettings.json │ │ ├── serviceDependencies.json │ │ └── serviceDependencies.local.json │ ├── Startup.cs │ ├── TodoList-WebApi.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── daemon-console.sln └── daemon-console │ ├── Daemon-Console.csproj │ ├── Program.cs │ └── appsettings.json ├── 3-Using-KeyVault ├── README.md └── ReadmeFiles │ ├── daemon-with-certificate.svg │ ├── daemon-with-secret.svg │ ├── topology-certificates.png │ └── topology.png ├── 4-Call-OwnApi-Pop ├── AppCreationScripts-withCert │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json ├── Microsoft.Identity.Web.Future │ ├── AadIssuerValidator.cs │ ├── IssuerConfigurationRetriever.cs │ ├── IssuerMetadata.cs │ ├── Metadata.cs │ ├── Microsoft.Identity.Web.Future.csproj │ ├── Microsoft.Identity.Web.Future.sln │ ├── Microsoft.Identity.Web.ruleset │ ├── README.md │ ├── SignedHttpRequest │ │ ├── Events │ │ │ ├── SignedHttpRequestAuthenticationFailedContext.cs │ │ │ ├── SignedHttpRequestEvents.cs │ │ │ ├── SignedHttpRequestMessageReceivedContext.cs │ │ │ └── SignedHttpRequestValidatedContext.cs │ │ ├── SignedHttpRequestAuthenticationHandler.cs │ │ ├── SignedHttpRequestDefaults.cs │ │ ├── SignedHttpRequestExtensions.cs │ │ ├── SignedHttpRequestLoggingExtensions.cs │ │ ├── SignedHttpRequestOptions.cs │ │ └── SignedHttpRequestPostConfigureOptions.cs │ └── WebApiServiceCollectionExtensions.cs ├── README.md ├── ReadmeFiles │ ├── daemon-with-certificate.svg │ ├── daemon-with-secret.svg │ ├── topology-certificates.png │ └── topology.png ├── TodoList-WebApi │ ├── Controllers │ │ └── TodoListController.cs │ ├── Models │ │ └── TodoItem.cs │ ├── Program.cs │ ├── Properties │ │ ├── launchSettings.json │ │ ├── serviceDependencies.json │ │ └── serviceDependencies.local.json │ ├── Startup.cs │ ├── TodoList-WebApi.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── daemon-console.sln └── daemon-console │ ├── AuthenticationConfig.cs │ ├── Daemon-Console.csproj │ ├── Program.cs │ ├── ProtectedApiCallHelper.cs │ └── appsettings.json ├── 5-Call-MSGraph-ManagedIdentity ├── Daemon-Console.sln ├── README.md └── daemon-console │ ├── Daemon-Console.csproj │ └── Program.cs ├── 6-Call-OwnApi-ManagedIdentity ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json ├── README.md ├── TodoList-WebApi │ ├── Controllers │ │ └── TodoListController.cs │ ├── Models │ │ └── TodoItem.cs │ ├── Program.cs │ ├── Properties │ │ ├── launchSettings.json │ │ ├── serviceDependencies.json │ │ └── serviceDependencies.local.json │ ├── Startup.cs │ ├── TodoList-WebApi.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── daemon-console-calls-api-msi.sln └── daemon-console │ ├── Daemon-Console.csproj │ ├── Program.cs │ └── appsettings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── build.bat └── buildAllSlns.proj /.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/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Please do NOT file bugs without filling in this form. 4 | title: '[Bug] ' 5 | labels: ["untriaged", "needs attention"] 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Which version of Microsoft.Identity.Web are you using?** 11 | 12 | Note that to get help, you need to run the latest version. 13 | 14 | 15 | **Is this a new or an existing app?** 16 | 21 | 22 | **Repro** 23 | 24 | ```csharp 25 | var your = (code) => here; 26 | ``` 27 | 28 | **Expected behavior** 29 | A clear and concise description of what you expected to happen (or code). 30 | 31 | **Actual behavior** 32 | A clear and concise description of what happens, e.g. an exception is thrown, UI freezes. 33 | 34 | **Possible solution** 35 | 36 | 37 | **Additional context / logs / screenshots / links to code** 38 | 39 | Add any other context about the problem here, such as logs and screenshots or links to code. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" 9 | directories: 10 | - /1-Call-MSGraph/ 11 | - /2-Call-OwnApi/ 12 | - /3-Using-KeyVault/ 13 | - /4-Call-OwnApi-Pop/ 14 | - /5-Call-MSGraph-ManagedIdentity/ 15 | - /6-Call-OwnApi-ManagedIdentity/ 16 | schedule: 17 | interval: "daily" 18 | allow: 19 | - dependency-name: "Microsoft.*" 20 | labels: 21 | - "dependabot" 22 | - "dependencies" 23 | -------------------------------------------------------------------------------- /.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 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | #**/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | /AppCreationScripts/createdApps.html 332 | /1-Call-MSGraph/AppCreationScripts/createdApps.html 333 | /1-Call-MSGraph/AppCreationScripts-withCert/createdApps.html 334 | /2-Call-OwnApi/AppCreationScripts/createdApps.html 335 | /1-Call-MSGraph/.vscode 336 | /.SharedData 337 | -------------------------------------------------------------------------------- /1-Call-MSGraph/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering sample apps with the Microsoft identity platform and updating configuration files using PowerShell 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. 8 | 9 | ```PowerShell 10 | cd .\AppCreationScripts\ 11 | .\Configure.ps1 -TenantId "your test tenant's id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" 12 | ``` 13 | 14 | ### More details 15 | 16 | - [Goal of the provided scripts](#goal-of-the-provided-scripts) 17 | - [Presentation of the scripts](#presentation-of-the-scripts) 18 | - [Usage pattern for tests and DevOps scenarios](#usage-pattern-for-tests-and-DevOps-scenarios) 19 | - [How to use the app creation scripts?](#how-to-use-the-app-creation-scripts) 20 | - [Pre-requisites](#pre-requisites) 21 | - [Run the script and start running](#run-the-script-and-start-running) 22 | - [Four ways to run the script](#four-ways-to-run-the-script) 23 | - [Option 1 (interactive)](#option-1-interactive) 24 | - [Option 2 (Interactive, but create apps in a specified tenant)](#option-3-Interactive-but-create-apps-in-a-specified-tenant) 25 | - [Running the script on Azure Sovereign clouds](#running-the-script-on-Azure-Sovereign-clouds) 26 | 27 | ## Goal of the provided scripts 28 | 29 | ### Presentation of the scripts 30 | 31 | 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. 32 | 33 | These scripts are: 34 | 35 | - `Configure.ps1` which: 36 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets, app roles), 37 | - changes the configuration files in the sample projects. 38 | - 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: 39 | - the identifier of the application 40 | - the AppId of the application 41 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 42 | 43 | - `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`). 44 | 45 | ### Usage pattern for tests and DevOps scenarios 46 | 47 | 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. 48 | 49 | ## How to use the app creation scripts? 50 | 51 | ### Pre-requisites 52 | 53 | 1. Powershell 7 or later 54 | 1. You can follow the instrunctions to install PowerShell at [this link](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3) 55 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 56 | 57 | ### (Optionally) install Microsoft.Graph.Applications PowerShell modules 58 | 59 | The scripts install the required PowerShell module (Microsoft.Graph.Applications) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: 60 | 61 | 1. If you have never done it already, in the PowerShell window, install the Microsoft.Graph.Applications PowerShell modules. For this: 62 | 63 | 1. Open PowerShell 64 | 2. Type: 65 | 66 | ```PowerShell 67 | Install-Module Microsoft.Graph.Applications 68 | ``` 69 | 70 | or if you want the modules to be installed for the current user only, run: 71 | 72 | ```PowerShell 73 | Install-Module Microsoft.Graph.Applications -Scope CurrentUser 74 | ``` 75 | 76 | ### Run the script and start running 77 | 78 | 1. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 79 | 80 | ```PowerShell 81 | cd AppCreationScripts 82 | ``` 83 | 84 | 1. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 85 | 1. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 86 | 1. select **Start** for the projects 87 | 88 | You're done! 89 | 90 | ### Two ways to run the script 91 | 92 | We advise four ways of running the script: 93 | 94 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 95 | - 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, 96 | 97 | Here are the details on how to do this. 98 | 99 | #### Option 1 (interactive) 100 | 101 | - Just run ``.\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 102 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 103 | 104 | 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. 105 | 106 | #### Option 2 (Interactive, but create apps in a specified tenant) 107 | 108 | if you want to create the apps in a particular tenant, you can use the following option: 109 | 110 | - Open the [Microsoft Entra admin center](https://entra.microsoft.com) 111 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 112 | - Find the "Active Directory" object in this tenant 113 | - Go to **Properties** and copy the content of the **Directory Id** property 114 | - Then use the full syntax to run the scripts: 115 | 116 | ```PowerShell 117 | $tenantId = "yourTenantIdGuid" 118 | . .\Cleanup.ps1 -TenantId $tenantId 119 | . .\Configure.ps1 -TenantId $tenantId 120 | ``` 121 | 122 | ### Running the script on Azure Sovereign clouds 123 | 124 | 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`. 125 | 126 | The acceptable values for this parameter are: 127 | 128 | - AzureCloud 129 | - AzureChinaCloud 130 | - AzureUSGovernment 131 | 132 | Example: 133 | 134 | ```PowerShell 135 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureUSGovernment" 136 | . .\Configure.ps1 -AzureEnvironmentName "AzureUSGovernment" 137 | ``` 138 | -------------------------------------------------------------------------------- /1-Call-MSGraph/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7 2 | 3 | [CmdletBinding()] 4 | param( 5 | [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')] 6 | [string] $tenantId, 7 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] 8 | [string] $azureEnvironmentName 9 | ) 10 | 11 | 12 | Function Cleanup 13 | { 14 | if (!$azureEnvironmentName) 15 | { 16 | $azureEnvironmentName = "Global" 17 | } 18 | 19 | <# 20 | .Description 21 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 22 | #> 23 | 24 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 25 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 26 | 27 | # Connect to the Microsoft Graph API 28 | Write-Host "Connecting to Microsoft Graph" 29 | 30 | 31 | if ($tenantId -eq "") 32 | { 33 | Connect-MgGraph -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 34 | } 35 | else 36 | { 37 | Connect-MgGraph -TenantId $tenantId -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 38 | } 39 | 40 | $context = Get-MgContext 41 | $tenantId = $context.TenantId 42 | 43 | # Get the user running the script 44 | $currentUserPrincipalName = $context.Account 45 | $user = Get-MgUser -Filter "UserPrincipalName eq '$($context.Account)'" 46 | 47 | # get the tenant we signed in to 48 | $Tenant = Get-MgOrganization 49 | $tenantName = $Tenant.DisplayName 50 | 51 | $verifiedDomain = $Tenant.VerifiedDomains | where {$_.Isdefault -eq $true} 52 | $verifiedDomainName = $verifiedDomain.Name 53 | $tenantId = $Tenant.Id 54 | 55 | Write-Host ("Connected to Tenant {0} ({1}) as account '{2}'. Domain is '{3}'" -f $Tenant.DisplayName, $Tenant.Id, $currentUserPrincipalName, $verifiedDomainName) 56 | 57 | # Removes the applications 58 | Write-Host "Cleaning-up applications from tenant '$tenantId'" 59 | 60 | Write-Host "Removing 'client' (daemon-console) if needed" 61 | try 62 | { 63 | Get-MgApplication -Filter "DisplayName eq 'daemon-console'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id } 64 | } 65 | catch 66 | { 67 | $message = $_ 68 | Write-Warning $Error[0] 69 | Write-Host "Unable to remove the application 'daemon-console'. Error is $message. Try deleting manually." -ForegroundColor White -BackgroundColor Red 70 | } 71 | 72 | Write-Host "Making sure there are no more (daemon-console) applications found, will remove if needed..." 73 | $apps = Get-MgApplication -Filter "DisplayName eq 'daemon-console'" | Format-List Id, DisplayName, AppId, SignInAudience, PublisherDomain 74 | 75 | if ($apps) 76 | { 77 | Remove-MgApplication -ApplicationId $apps.Id 78 | } 79 | 80 | foreach ($app in $apps) 81 | { 82 | Remove-MgApplication -ApplicationId $app.Id 83 | Write-Host "Removed daemon-console.." 84 | } 85 | 86 | # also remove service principals of this app 87 | try 88 | { 89 | Get-MgServicePrincipal -filter "DisplayName eq 'daemon-console'" | ForEach-Object {Remove-MgServicePrincipal -ServicePrincipalId $_.Id -Confirm:$false} 90 | } 91 | catch 92 | { 93 | $message = $_ 94 | Write-Warning $Error[0] 95 | Write-Host "Unable to remove ServicePrincipal 'daemon-console'. Error is $message. Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red 96 | } 97 | } 98 | 99 | # Pre-requisites 100 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph")) { 101 | Install-Module "Microsoft.Graph" -Scope CurrentUser 102 | } 103 | 104 | #Import-Module Microsoft.Graph 105 | 106 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Authentication")) { 107 | Install-Module "Microsoft.Graph.Authentication" -Scope CurrentUser 108 | } 109 | 110 | Import-Module Microsoft.Graph.Authentication 111 | 112 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Identity.DirectoryManagement")) { 113 | Install-Module "Microsoft.Graph.Identity.DirectoryManagement" -Scope CurrentUser 114 | } 115 | 116 | Import-Module Microsoft.Graph.Identity.DirectoryManagement 117 | 118 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { 119 | Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser 120 | } 121 | 122 | Import-Module Microsoft.Graph.Applications 123 | 124 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Groups")) { 125 | Install-Module "Microsoft.Graph.Groups" -Scope CurrentUser 126 | } 127 | 128 | Import-Module Microsoft.Graph.Groups 129 | 130 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Users")) { 131 | Install-Module "Microsoft.Graph.Users" -Scope CurrentUser 132 | } 133 | 134 | Import-Module Microsoft.Graph.Users 135 | 136 | $ErrorActionPreference = "Stop" 137 | 138 | 139 | try 140 | { 141 | Cleanup -tenantId $tenantId -environment $azureEnvironmentName 142 | } 143 | catch 144 | { 145 | $_.Exception.ToString() | out-host 146 | $message = $_ 147 | Write-Warning $Error[0] 148 | Write-Host "Unable to register apps. Error is $message." -ForegroundColor White -BackgroundColor Red 149 | } 150 | 151 | Write-Host "Disconnecting from tenant" 152 | Disconnect-MgGraph 153 | -------------------------------------------------------------------------------- /1-Call-MSGraph/AppCreationScripts/apps.json: -------------------------------------------------------------------------------- 1 | { 2 | /* 3 | This section describes the Azure AD Applications to configure, and their dependencies 4 | */ 5 | 6 | "Sample": { 7 | "Title": "Acquire a token and call Microsoft Graph API from a console app using app's identity", 8 | "Level": 300, 9 | "Client": "ASP.NET Core 2.1" 10 | }, 11 | "AppRegistrations": [ 12 | { 13 | "x-ms-id": "active-directory-dotnetcore-daemon-v2", 14 | "x-ms-name": "dotnetcore-daemon-v2", 15 | "x-ms-version": "2.0", 16 | "passwordCredentials": [ 17 | { 18 | "value": "{auto}" 19 | } 20 | ], 21 | "requiredResourceAccess": [ 22 | { 23 | "x-ms-resourceAppName": "Microsoft Graph", 24 | "resourceAppId": "00000003-0000-0000-c000-000000000000", 25 | "resourceAccess": [ 26 | { 27 | "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d", 28 | "type": "Scope", 29 | "x-ms-name": "User.Read" 30 | } 31 | ] 32 | }, 33 | { 34 | "x-ms-resourceAppName": "Microsoft Graph", 35 | "resourceAppId": "00000003-0000-0000-c000-000000000000", 36 | "resourceAccess": [ 37 | { 38 | "id": "df021288-bdef-4463-88db-98f22de89214", 39 | "type": "Role", 40 | "x-ms-name": "User.Read.All" 41 | } 42 | ] 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /1-Call-MSGraph/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "A .NET Core 2.x simple daemon console application calling the graph with its own identity", 4 | "Level": 200, 5 | "Client": ".NET Core (Console)", 6 | "Service": "Microsoft Graph", 7 | "RepositoryUrl": "active-directory-dotnetcore-daemon-v2", 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": "daemon-console", 18 | "Kind": "Daemon", 19 | "Audience": "AzureADMyOrg", 20 | "PasswordCredentials": "Auto", 21 | "UsesROPCOrIWA": false, 22 | "ReplyUrls": "https://daemon", 23 | "SDK": "MicrosoftIdentityWeb", 24 | "SampleSubPath": "1-Call-MSGraph\\daemon-console", 25 | "RequiredResourcesAccess": [ 26 | { 27 | "Resource": "Microsoft Graph", 28 | "ApplicationPermissions": [ "User.Read.All" ] 29 | } 30 | ], 31 | "ManualSteps": [ 32 | { 33 | "Comment" : "Navigate to the API permissions page and click on 'Grant admin consent for {tenant}'" 34 | } 35 | ] 36 | } 37 | ], 38 | 39 | /* 40 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 41 | are created in Azure AD. 42 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 43 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 44 | */ 45 | "CodeConfiguration": [ 46 | { 47 | "App": "client", 48 | "SettingKind": "JSon", 49 | "SettingFile": "\\..\\daemon-console\\appsettings.json", 50 | "Mappings": [ 51 | { 52 | "key": "TenantId", 53 | "value": "$tenantName" 54 | }, 55 | { 56 | "key": "ClientId", 57 | "value": ".AppId" 58 | }, 59 | { 60 | "key": "ClientSecret`\":", 61 | "value": ".AppKey" 62 | } 63 | ] 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /1-Call-MSGraph/ReadmeFiles/topology-certificates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/1-Call-MSGraph/ReadmeFiles/topology-certificates.png -------------------------------------------------------------------------------- /1-Call-MSGraph/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/1-Call-MSGraph/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /1-Call-MSGraph/daemon-console.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "daemon-console", "daemon-console\daemon-console.csproj", "{DE92BA34-DBCA-4087-BBAB-85AC833031BA}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{241AB638-4786-435A-8565-93AD3C40BBB4}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {5A9A2601-8CEB-4475-9E95-43A5438B71CD} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /1-Call-MSGraph/daemon-console/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Graph; 7 | using Microsoft.Identity.Abstractions; 8 | using Microsoft.Identity.Web; 9 | using System; 10 | using System.Threading.Tasks; 11 | 12 | namespace daemon_console 13 | { 14 | /// 15 | /// This sample shows how to query the Microsoft Graph from a daemon application 16 | /// 17 | class Program 18 | { 19 | static async Task Main(string[] _) 20 | { 21 | // Get the Token acquirer factory instance. By default it reads an appsettings.json 22 | // file if it exists in the same folder as the app (make sure that the 23 | // "Copy to Output Directory" property of the appsettings.json file is "Copy if newer"). 24 | TokenAcquirerFactory tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance(); 25 | 26 | // Configure the application options to be read from the configuration 27 | // and add the services you need (Graph, token cache) 28 | IServiceCollection services = tokenAcquirerFactory.Services; 29 | services.AddMicrosoftGraph(); 30 | // By default, you get an in-memory token cache. 31 | // For more token cache serialization options, see https://aka.ms/msal-net-token-cache-serialization 32 | 33 | // Resolve the dependency injection. 34 | var serviceProvider = tokenAcquirerFactory.Build(); 35 | 36 | // Call Microsoft Graph using the Graph SDK 37 | try 38 | { 39 | GraphServiceClient graphServiceClient = serviceProvider.GetRequiredService(); 40 | var users = await graphServiceClient.Users 41 | .GetAsync(r => r.Options.WithAppOnly()); 42 | Console.WriteLine($"{users.Value.Count} users"); 43 | } 44 | catch (ServiceException e) 45 | { 46 | Console.WriteLine("We could not retrieve the user's list: " + $"{e}"); 47 | 48 | // If you get the following exception, here is what you need to do 49 | // --------------------------------------------------------------- 50 | // IDW10503: Cannot determine the cloud Instance. 51 | // Provide the configuration (appsettings.json with an "AzureAd" section, and "Instance" set, 52 | // the project needs to be this way) 53 | // 54 | // < None Update = "appsettings.json" > 55 | // < CopyToOutputDirectory > PreserveNewest 56 | // 57 | // 58 | // System.ArgumentNullException: Value cannot be null. (Parameter 'tenantId') 59 | // Provide the TenantId in the configuration 60 | // Microsoft.Identity.Client.MsalClientException: No ClientId was specified. 61 | // Provide the ClientId in the configuration 62 | // ErrorCode: Client_Credentials_Required_In_Confidential_Client_Application 63 | // Provide a ClientCredentials section containing either a client secret, or a certificate 64 | // or workload identity federation for Kubernates if your app runs in AKS 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /1-Call-MSGraph/daemon-console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AzureAd": { 3 | "Instance": "https://login.microsoftonline.com/", 4 | "TenantId": "[Enter here the tenantID or domain name for your Azure AD tenant]", 5 | "ClientId": "[Enter here the ClientId for your application]", 6 | "ClientCredentials": [ 7 | { 8 | "SourceType": "ClientSecret", 9 | "ClientSecret": "[Enter here a client secret for your application]" 10 | } 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /1-Call-MSGraph/daemon-console/daemon-console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | daemon_console 7 | b7366876-bf99-444e-bdb6-af091d4f9556 8 | 9 | 10 | 11 | TRACE 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /2-Call-OwnApi/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering sample apps with the Microsoft identity platform and updating configuration files using PowerShell 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. 8 | 9 | ```PowerShell 10 | cd .\AppCreationScripts\ 11 | .\Configure.ps1 -TenantId "your test tenant's id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" 12 | ``` 13 | 14 | ### More details 15 | 16 | - [Goal of the provided scripts](#goal-of-the-provided-scripts) 17 | - [Presentation of the scripts](#presentation-of-the-scripts) 18 | - [Usage pattern for tests and DevOps scenarios](#usage-pattern-for-tests-and-DevOps-scenarios) 19 | - [How to use the app creation scripts?](#how-to-use-the-app-creation-scripts) 20 | - [Pre-requisites](#pre-requisites) 21 | - [Run the script and start running](#run-the-script-and-start-running) 22 | - [Four ways to run the script](#four-ways-to-run-the-script) 23 | - [Option 1 (interactive)](#option-1-interactive) 24 | - [Option 2 (Interactive, but create apps in a specified tenant)](#option-3-Interactive-but-create-apps-in-a-specified-tenant) 25 | - [Running the script on Azure Sovereign clouds](#running-the-script-on-Azure-Sovereign-clouds) 26 | 27 | ## Goal of the provided scripts 28 | 29 | ### Presentation of the scripts 30 | 31 | 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. 32 | 33 | These scripts are: 34 | 35 | - `Configure.ps1` which: 36 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets, app roles), 37 | - changes the configuration files in the sample projects. 38 | - 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: 39 | - the identifier of the application 40 | - the AppId of the application 41 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 42 | 43 | - `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`). 44 | 45 | ### Usage pattern for tests and DevOps scenarios 46 | 47 | 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. 48 | 49 | ## How to use the app creation scripts? 50 | 51 | ### Pre-requisites 52 | 53 | 1. Powershell 7 or later 54 | 1. You can follow the instrunctions to install PowerShell at [this link](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3) 55 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 56 | 57 | ### (Optionally) install Microsoft.Graph.Applications PowerShell modules 58 | 59 | The scripts install the required PowerShell module (Microsoft.Graph.Applications) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: 60 | 61 | 1. If you have never done it already, in the PowerShell window, install the Microsoft.Graph.Applications PowerShell modules. For this: 62 | 63 | 1. Open PowerShell 64 | 2. Type: 65 | 66 | ```PowerShell 67 | Install-Module Microsoft.Graph.Applications 68 | ``` 69 | 70 | or if you want the modules to be installed for the current user only, run: 71 | 72 | ```PowerShell 73 | Install-Module Microsoft.Graph.Applications -Scope CurrentUser 74 | ``` 75 | 76 | ### Run the script and start running 77 | 78 | 1. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 79 | 80 | ```PowerShell 81 | cd AppCreationScripts 82 | ``` 83 | 84 | 1. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 85 | 1. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 86 | 1. select **Start** for the projects 87 | 88 | You're done! 89 | 90 | ### Two ways to run the script 91 | 92 | We advise four ways of running the script: 93 | 94 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 95 | - 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, 96 | 97 | Here are the details on how to do this. 98 | 99 | #### Option 1 (interactive) 100 | 101 | - Just run ``.\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 102 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 103 | 104 | 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. 105 | 106 | #### Option 2 (Interactive, but create apps in a specified tenant) 107 | 108 | if you want to create the apps in a particular tenant, you can use the following option: 109 | 110 | - Open the [Microsoft Entra admin center](https://entra.microsoft.com) 111 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 112 | - Find the "Active Directory" object in this tenant 113 | - Go to **Properties** and copy the content of the **Directory Id** property 114 | - Then use the full syntax to run the scripts: 115 | 116 | ```PowerShell 117 | $tenantId = "yourTenantIdGuid" 118 | . .\Cleanup.ps1 -TenantId $tenantId 119 | . .\Configure.ps1 -TenantId $tenantId 120 | ``` 121 | 122 | ### Running the script on Azure Sovereign clouds 123 | 124 | 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`. 125 | 126 | The acceptable values for this parameter are: 127 | 128 | - AzureCloud 129 | - AzureChinaCloud 130 | - AzureUSGovernment 131 | 132 | Example: 133 | 134 | ```PowerShell 135 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureUSGovernment" 136 | . .\Configure.ps1 -AzureEnvironmentName "AzureUSGovernment" 137 | ``` 138 | -------------------------------------------------------------------------------- /2-Call-OwnApi/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7 2 | 3 | [CmdletBinding()] 4 | param( 5 | [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')] 6 | [string] $tenantId, 7 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] 8 | [string] $azureEnvironmentName 9 | ) 10 | 11 | 12 | Function Cleanup 13 | { 14 | if (!$azureEnvironmentName) 15 | { 16 | $azureEnvironmentName = "Global" 17 | } 18 | 19 | <# 20 | .Description 21 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 22 | #> 23 | 24 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 25 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 26 | 27 | # Connect to the Microsoft Graph API 28 | Write-Host "Connecting to Microsoft Graph" 29 | 30 | 31 | if ($tenantId -eq "") 32 | { 33 | Connect-MgGraph -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 34 | } 35 | else 36 | { 37 | Connect-MgGraph -TenantId $tenantId -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 38 | } 39 | 40 | $context = Get-MgContext 41 | $tenantId = $context.TenantId 42 | 43 | # Get the user running the script 44 | $currentUserPrincipalName = $context.Account 45 | $user = Get-MgUser -Filter "UserPrincipalName eq '$($context.Account)'" 46 | 47 | # get the tenant we signed in to 48 | $Tenant = Get-MgOrganization 49 | $tenantName = $Tenant.DisplayName 50 | 51 | $verifiedDomain = $Tenant.VerifiedDomains | where {$_.Isdefault -eq $true} 52 | $verifiedDomainName = $verifiedDomain.Name 53 | $tenantId = $Tenant.Id 54 | 55 | Write-Host ("Connected to Tenant {0} ({1}) as account '{2}'. Domain is '{3}'" -f $Tenant.DisplayName, $Tenant.Id, $currentUserPrincipalName, $verifiedDomainName) 56 | 57 | # Removes the applications 58 | Write-Host "Cleaning-up applications from tenant '$tenantId'" 59 | 60 | Write-Host "Removing 'service' (TodoList-webapi-daemon-v2) if needed" 61 | try 62 | { 63 | Get-MgApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id } 64 | } 65 | catch 66 | { 67 | $message = $_ 68 | Write-Warning $Error[0] 69 | Write-Host "Unable to remove the application 'TodoList-webapi-daemon-v2'. Error is $message. Try deleting manually." -ForegroundColor White -BackgroundColor Red 70 | } 71 | 72 | Write-Host "Making sure there are no more (TodoList-webapi-daemon-v2) applications found, will remove if needed..." 73 | $apps = Get-MgApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | Format-List Id, DisplayName, AppId, SignInAudience, PublisherDomain 74 | 75 | if ($apps) 76 | { 77 | Remove-MgApplication -ApplicationId $apps.Id 78 | } 79 | 80 | foreach ($app in $apps) 81 | { 82 | Remove-MgApplication -ApplicationId $app.Id 83 | Write-Host "Removed TodoList-webapi-daemon-v2.." 84 | } 85 | 86 | # also remove service principals of this app 87 | try 88 | { 89 | Get-MgServicePrincipal -filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-MgServicePrincipal -ServicePrincipalId $_.Id -Confirm:$false} 90 | } 91 | catch 92 | { 93 | $message = $_ 94 | Write-Warning $Error[0] 95 | Write-Host "Unable to remove ServicePrincipal 'TodoList-webapi-daemon-v2'. Error is $message. Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red 96 | } 97 | Write-Host "Removing 'client' (daemon-console-v2) if needed" 98 | try 99 | { 100 | Get-MgApplication -Filter "DisplayName eq 'daemon-console-v2'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id } 101 | } 102 | catch 103 | { 104 | $message = $_ 105 | Write-Warning $Error[0] 106 | Write-Host "Unable to remove the application 'daemon-console-v2'. Error is $message. Try deleting manually." -ForegroundColor White -BackgroundColor Red 107 | } 108 | 109 | Write-Host "Making sure there are no more (daemon-console-v2) applications found, will remove if needed..." 110 | $apps = Get-MgApplication -Filter "DisplayName eq 'daemon-console-v2'" | Format-List Id, DisplayName, AppId, SignInAudience, PublisherDomain 111 | 112 | if ($apps) 113 | { 114 | Remove-MgApplication -ApplicationId $apps.Id 115 | } 116 | 117 | foreach ($app in $apps) 118 | { 119 | Remove-MgApplication -ApplicationId $app.Id 120 | Write-Host "Removed daemon-console-v2.." 121 | } 122 | 123 | # also remove service principals of this app 124 | try 125 | { 126 | Get-MgServicePrincipal -filter "DisplayName eq 'daemon-console-v2'" | ForEach-Object {Remove-MgServicePrincipal -ServicePrincipalId $_.Id -Confirm:$false} 127 | } 128 | catch 129 | { 130 | $message = $_ 131 | Write-Warning $Error[0] 132 | Write-Host "Unable to remove ServicePrincipal 'daemon-console-v2'. Error is $message. Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red 133 | } 134 | } 135 | 136 | # Pre-requisites 137 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph")) { 138 | Install-Module "Microsoft.Graph" -Scope CurrentUser 139 | } 140 | 141 | #Import-Module Microsoft.Graph 142 | 143 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Authentication")) { 144 | Install-Module "Microsoft.Graph.Authentication" -Scope CurrentUser 145 | } 146 | 147 | Import-Module Microsoft.Graph.Authentication 148 | 149 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Identity.DirectoryManagement")) { 150 | Install-Module "Microsoft.Graph.Identity.DirectoryManagement" -Scope CurrentUser 151 | } 152 | 153 | Import-Module Microsoft.Graph.Identity.DirectoryManagement 154 | 155 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { 156 | Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser 157 | } 158 | 159 | Import-Module Microsoft.Graph.Applications 160 | 161 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Groups")) { 162 | Install-Module "Microsoft.Graph.Groups" -Scope CurrentUser 163 | } 164 | 165 | Import-Module Microsoft.Graph.Groups 166 | 167 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Users")) { 168 | Install-Module "Microsoft.Graph.Users" -Scope CurrentUser 169 | } 170 | 171 | Import-Module Microsoft.Graph.Users 172 | 173 | $ErrorActionPreference = "Stop" 174 | 175 | 176 | try 177 | { 178 | Cleanup -tenantId $tenantId -environment $azureEnvironmentName 179 | } 180 | catch 181 | { 182 | $_.Exception.ToString() | out-host 183 | $message = $_ 184 | Write-Warning $Error[0] 185 | Write-Host "Unable to register apps. Error is $message." -ForegroundColor White -BackgroundColor Red 186 | } 187 | 188 | Write-Host "Disconnecting from tenant" 189 | Disconnect-MgGraph 190 | -------------------------------------------------------------------------------- /2-Call-OwnApi/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "A .NET Core daemon console application calling a custom Web API with its own identity", 4 | "Level": 200, 5 | "Client": ".NET Core (Console)", 6 | "Service": ".NET Core Web API", 7 | "RepositoryUrl": "active-directory-dotnetcore-daemon-v2", 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": "service", 17 | "Name": "TodoList-webapi-daemon-v2", 18 | "Kind": "WebApi", 19 | "Audience": "AzureADMyOrg", 20 | "HomePage": "https://localhost:44372", 21 | "SDK": "MicrosoftIdentityWeb", 22 | "SampleSubPath": "2-Call-OwnApi\\TodoList-WebApi", 23 | "AppRoles": [ 24 | { 25 | "Types" : ["Application"], 26 | "Name" : "DaemonAppRole", 27 | "Description" : "Daemon apps in this role can consume the web api." 28 | } 29 | ] 30 | }, 31 | { 32 | "Id": "client", 33 | "Name": "daemon-console-v2", 34 | "Kind": "Daemon", 35 | "Audience": "AzureADMyOrg", 36 | "PasswordCredentials": "Auto", 37 | "UsesROPCOrIWA": false, 38 | "SDK": "MicrosoftIdentityWeb", 39 | "SampleSubPath": "2-Call-OwnApi\\daemon-console", 40 | "ReplyUrls": "https://daemon", 41 | "RequiredResourcesAccess": [ 42 | { 43 | "Resource": "service", 44 | "ApplicationPermissions": [ "DaemonAppRole" ] 45 | } 46 | ], 47 | "ManualSteps": [ 48 | { 49 | "Comment" : "Navigate to the API permissions page and click on 'Grant admin consent for {tenant}'" 50 | } 51 | ] 52 | } 53 | ], 54 | 55 | /* 56 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 57 | are created in Azure AD. 58 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 59 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 60 | */ 61 | "CodeConfiguration": [ 62 | { 63 | "App": "service", 64 | "SettingKind": "Text", 65 | "SettingFile": "\\..\\TodoList-WebApi\\appsettings.json", 66 | "Mappings": [ 67 | { 68 | "key": "Domain", 69 | "value": "$tenantName" 70 | }, 71 | { 72 | "key": "TenantId", 73 | "value": "$tenantId" 74 | }, 75 | { 76 | "key": "ClientId", 77 | "value": "service.AppId" 78 | } 79 | ] 80 | }, 81 | { 82 | "App": "client", 83 | "SettingKind": "JSon", 84 | "SettingFile": "\\..\\Daemon-Console\\appsettings.json", 85 | "Mappings": [ 86 | { 87 | "key": "Tenant", 88 | "value": "$tenantName" 89 | }, 90 | { 91 | "key": "ClientId", 92 | "value": ".AppId" 93 | }, 94 | { 95 | "key": "ClientSecret`\":", 96 | "value": ".AppKey" 97 | }, 98 | { 99 | "key": "TodoListScope", 100 | "value": "service.ScopeDefault" 101 | }, 102 | { 103 | "key": "TodoListBaseAddress", 104 | "value": "service.HomePage" 105 | } 106 | ] 107 | }, 108 | { 109 | "App": "client", 110 | "SettingKind": "Replace", 111 | "SettingFile": "\\..\\Daemon-Console\\appsettings.json", 112 | "Mappings": [ 113 | { 114 | "key": "[Enter here the scopes for your web API]", 115 | "value": "\"api://\"+$serviceAadApplication.AppId+\"/.default\"" 116 | } 117 | ] 118 | } 119 | ] 120 | } 121 | -------------------------------------------------------------------------------- /2-Call-OwnApi/ReadmeFiles/topology-certificates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/2-Call-OwnApi/ReadmeFiles/topology-certificates.png -------------------------------------------------------------------------------- /2-Call-OwnApi/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/2-Call-OwnApi/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/Controllers/TodoListController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Identity.Web.Resource; 7 | using System.Collections.Generic; 8 | using TodoList_WebApi.Models; 9 | 10 | namespace TodoList_WebApi.Controllers 11 | { 12 | [Authorize] 13 | [Route("api/[controller]")] 14 | [ApiController] 15 | public class TodoListController : ControllerBase 16 | { 17 | // In-memory TodoList 18 | private static readonly Dictionary TodoStore = new Dictionary(); 19 | 20 | public TodoListController() 21 | { 22 | // Pre-populate with sample data 23 | if (TodoStore.Count == 0) 24 | { 25 | TodoStore.Add(1, new TodoItem() { Id = 1, Task = "Pick up groceries" }); 26 | TodoStore.Add(2, new TodoItem() { Id = 2, Task = "Finish invoice report" }); 27 | TodoStore.Add(3, new TodoItem() { Id = 3, Task = "Water plants" }); 28 | } 29 | } 30 | 31 | // GET: api/todolist 32 | [HttpGet] 33 | [RequiredScopeOrAppPermission(AcceptedAppPermission = new[] { "DaemonAppRole" })] 34 | public IActionResult Get() 35 | { 36 | return Ok(TodoStore.Values); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/Models/TodoItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace TodoList_WebApi.Models 5 | { 6 | public class TodoItem 7 | { 8 | public int Id { get; set; } 9 | public string Task { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace TodoList_WebApi 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | CreateHostBuilder(args).Build().Run(); 14 | } 15 | 16 | public static IHostBuilder CreateHostBuilder(string[] args) => 17 | Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder.UseStartup(); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "https://localhost:44372", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "TodoList-WebApi": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:44372" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets.user" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Authentication.JwtBearer; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Identity.Web; 11 | using System.IdentityModel.Tokens.Jwt; 12 | 13 | namespace TodoList_WebApi 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 28 | .AddMicrosoftIdentityWebApi(Configuration); 29 | 30 | services.AddControllers(); 31 | } 32 | 33 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 34 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 35 | { 36 | if (env.IsDevelopment()) 37 | { 38 | // Since IdentityModel version 5.2.1 (or since Microsoft.AspNetCore.Authentication.JwtBearer version 2.2.0), 39 | // Personal Identifiable Information is not written to the logs by default, to be compliant with GDPR. 40 | // For debugging/development purposes, one can enable additional detail in exceptions by setting IdentityModelEventSource.ShowPII to true. 41 | // Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | else 45 | { 46 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 47 | app.UseHsts(); 48 | } 49 | 50 | app.UseHttpsRedirection(); 51 | 52 | app.UseRouting(); 53 | app.UseAuthentication(); 54 | app.UseAuthorization(); 55 | 56 | app.UseEndpoints(endpoints => 57 | { 58 | endpoints.MapControllers(); 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/TodoList-WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | InProcess 6 | TodoList_WebApi 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /2-Call-OwnApi/TodoList-WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AzureAd": { 3 | "Instance": "https://login.microsoftonline.com/", 4 | "TenantId": "[Enter here the tenantID or domain name for your Azure AD tenant]", 5 | "ClientId": "[Enter here the ClientId for your application]", 6 | "Scopes": "[Enter here the scopes for your web API]", 7 | "TokenDecryptionCredentials": [ 8 | ] 9 | }, 10 | "Logging": { 11 | "LogLevel": { 12 | "Default": "Information", 13 | "Microsoft.AspNetCore": "Warning" 14 | } 15 | }, 16 | "AllowedHosts": "*" 17 | } -------------------------------------------------------------------------------- /2-Call-OwnApi/daemon-console.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daemon-Console", "daemon-console\Daemon-Console.csproj", "{DE92BA34-DBCA-4087-BBAB-85AC833031BA}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{241AB638-4786-435A-8565-93AD3C40BBB4}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList-WebApi", "TodoList-WebApi\TodoList-WebApi.csproj", "{78B0FEAF-64DF-483B-A653-BF3110053FA3}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {5A9A2601-8CEB-4475-9E95-43A5438B71CD} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /2-Call-OwnApi/daemon-console/Daemon-Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | daemon_console 7 | 8 | 9 | 10 | TRACE 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /2-Call-OwnApi/daemon-console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Identity.Abstractions; 3 | using Microsoft.Identity.Web; 4 | using System.Collections.Generic; 5 | using System; 6 | using System.Linq; 7 | using TodoList_WebApi.Models; 8 | using Microsoft.Extensions.Logging; 9 | 10 | // Get the Token acquirer factory instance. By default it reads an appsettings.json 11 | // file if it exists in the same folder as the app (make sure that the 12 | // "Copy to Output Directory" property of the appsettings.json file is "Copy if newer"). 13 | var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance(); 14 | 15 | // Add console logging or other services if you wish 16 | tokenAcquirerFactory.Services.AddLogging( 17 | (loggingBuilder) => loggingBuilder.SetMinimumLevel(LogLevel.Warning) 18 | .AddConsole() 19 | ); 20 | 21 | // Create a downstream API service named 'MyApi' which comes loaded with several 22 | // utility methods to make HTTP calls to the DownstreamApi configurations found 23 | // in the "MyWebApi" section of your appsettings.json file. 24 | tokenAcquirerFactory.Services.AddDownstreamApi("MyApi", 25 | tokenAcquirerFactory.Configuration.GetSection("MyWebApi")); 26 | var sp = tokenAcquirerFactory.Build(); 27 | 28 | // Extract the downstream API service from the 'tokenAcquirerFactory' service provider. 29 | var api = sp.GetRequiredService(); 30 | 31 | // You can use the API service to make direct HTTP calls to your API. Token 32 | // acquisition is handled automatically based on the configurations in your 33 | // appsettings.json file. 34 | var result = await api.GetForAppAsync>("MyApi"); 35 | Console.WriteLine($"result = {result?.Count()}"); 36 | -------------------------------------------------------------------------------- /2-Call-OwnApi/daemon-console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AzureAd": { 3 | "Instance": "https://login.microsoftonline.com/", 4 | "TenantId": "[Enter here the tenantID or domain name for your Azure AD tenant]", 5 | "ClientId": "[Enter here the ClientId for your application]", 6 | "ClientCredentials": [ 7 | { 8 | "SourceType": "ClientSecret", 9 | "ClientSecret": "[Enter here a client secret for your application]" 10 | } 11 | ] 12 | }, 13 | 14 | "MyWebApi": { 15 | "BaseUrl": "https://localhost:44372/", 16 | "RelativePath": "api/TodoList", 17 | "RequestAppToken": true, 18 | "Scopes": [ "[Enter here the scopes for your web API]" ] // . E.g. 'api:///.default' 19 | } 20 | } -------------------------------------------------------------------------------- /3-Using-KeyVault/README.md: -------------------------------------------------------------------------------- 1 | # A .NET Core daemon console application calling a web API using a certificate stored in an Azure Key Vault 2 | 3 | ## Overview 4 | 5 | In this chapter, we explain how the [Call Microsoft Graph](../1-Call-MSGraph/README.md) or [Call Own API](../2-Call-OwnApi/README.md) samples can be configured to use credentials stored in the Key Vault instead of using a certificate or secret from a configuration file or a local machine certificate store. 6 | 7 | ## Scenario 8 | 9 | - Acquire a certificate stored in an Azure Key Vault 10 | - Use the certificate to acquire a token from Microsoft Identity Platform 11 | - Use the retrieved token from the Microsoft Identity Platform to call a protected API. Either the Microsoft Graph `/users` endpoint to get the list of users in the [Call Microsoft Graph](../1-Call-MSGraph/README.md) sample or a protected API of **TODO** objects in the [Call Own API](../2-Call-OwnApi/README.md) sample. 12 | 13 | ## Prerequisites 14 | 15 | To carry out these steps, you'd also need the following apart from the pre-requisites mentioned in the parent tutorial. 16 | 17 | - An [Azure subscription](https://azure.microsoft.com/free/). 18 | 19 | ## How to run samples using credentials from Key Vault 20 | 21 | You'll need: 22 | 23 | - [Visual Studio](https://aka.ms/vsdownload) and the [.NET Core SDK](https://www.microsoft.com/net/learn/get-started) 24 | - An Internet connection 25 | - A Windows machine (necessary if you want to run the app on Windows) 26 | - An OS X machine (necessary if you want to run the app on Mac) 27 | - A Linux machine (necessary if you want to run the app on Linux) 28 | - 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/documentation/articles/active-directory-howto-tenant/) 29 | 30 | ### Step 1: Clone or download this repository 31 | 32 | From your shell or command line: 33 | 34 | ```Shell 35 | git clone https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2.git 36 | ``` 37 | 38 | or download and exact the repository .zip file. 39 | 40 | If you want to build a sample that makes a call to the **Graph API** follow the [setup instructions]("1-Call-MSGraph") and return after you have successfully registered your application. 41 | 42 | If you want to build a sample that makes a call to a locally running API follow the [setup instructions]("2-Call-OwnApi") and return after you have successfully registered your application. 43 | 44 | ### Step 2: Create an Azure Key Vault with a certificate on your tenant 45 | 46 | In this step you'll need to create a Key Vault on your Azure tenant and then create store a certificate within that Key Vault. 47 | 48 | You can find the instructions for creating a Key Vault [here](https://docs.microsoft.com/azure/key-vault/general/quick-create-portal). 49 | 50 | After the Key Vault is created [upload your own certificate or create a new certificate entirely](https://docs.microsoft.com/azure/key-vault/certificates/tutorial-import-certificate) and store it in the Key Vault. To generate a certificate in the Microsoft Entra admin center select **Generate** as the **Method of Certificate Creation** instead of **Import** and fill in the configuration as appropriate. 51 | 52 | If you create a new certificate you should download a **CER** format copy of the certificate. You'll need it to [register the certificate with your application](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app#add-credentials). 53 | 54 | ### Step 3: Update the appsettings.json file to use the certificate information in your Key Vault 55 | 56 | In the `appsettings.json` file contained in the `daemon-console` directory of either app, replace the content of `ClientCredentials` with the following. Replace `` with the Vault URI value for your Key Vault and `` with the name of the certificate stored in your Key Vault. 57 | 58 | ```json 59 | "ClientCredentials": [ 60 | { 61 | "SourceType": "KeyVault", 62 | "KeyVaultUrl": "", 63 | "KeyVaultCertificateName": "" 64 | } 65 | ] 66 | ``` 67 | 68 | ### Step 4: Run the sample 69 | 70 | Start the application. 71 | 72 | If you're using Visual Studio run the app by cleaning the solution, rebuilding and then running it. 73 | 74 | > [Consider taking a moment to share your experience with us.](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbRy8G199fkJNDjJ9kJaxUJIhUNUJGSDU1UkxFMlRSWUxGVTlFVkpGT0tOTi4u) 75 | 76 | ## Community Help and Support 77 | 78 | Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. 79 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. 80 | Make sure that your questions or comments are tagged with [`msal` `dotnet`]. 81 | 82 | If you find a bug in the sample, please raise the issue on [GitHub Issues](../../issues). 83 | 84 | If you find a bug in msal.Net, please raise the issue on [MSAL.NET GitHub Issues](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues). 85 | 86 | To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory). 87 | 88 | ## Contributing 89 | 90 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md). 91 | 92 | 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. 93 | 94 | ## More information 95 | 96 | ### About Azure Key Vault 97 | 98 | Cloud applications and services use cryptographic keys and secrets to help keep information secure. [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) safeguards these keys and secrets. When you use Key Vault, you can encrypt authentication keys, storage account keys, data encryption keys, .pfx files, and passwords by using keys that are protected by hardware security modules (HSMs). 99 | 100 | ### About Managed Identities for Azure Resources 101 | 102 | Azure Key Vault provides a way to securely store credentials, secrets, and other keys, but your code has to authenticate to Key Vault to retrieve them. The [managed identities for Azure resources](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) feature in Microsoft Entra ID solves this problem. The feature provides Azure services with an automatically managed identity in Microsoft Entra ID. You can use the identity to authenticate to any service that supports Microsoft Entra authentication, including Key Vault, without any credentials in your code. 103 | 104 | In a daemon application scenario, Managed Identity will work if you have it deployed it in an [Azure Virtual Machine](https://azure.microsoft.com/services/virtual-machines/) or [Azure Web Job](https://docs.microsoft.com/azure/app-service/webjobs-create). Please, read [this documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) to understand how Managed Identity works with an Azure VM. 105 | 106 | #### Configure Managed identity on Azure VM to access Key Vault 107 | 108 | To authenticate to Key Vault using your Azure VM, you must first grant it permissions to Key Vault using the **Key Vault Access Policies**. To do that, follow the steps: 109 | 110 | 1. On Microsoft Entra admin center, note the name of the Azure VM where you deployed the daemon application. 111 | 1. [Enable managed identity on the virtual machine](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm). 112 | 1. On Microsoft Entra admin center, navigate to **Key Vaults** and select the one that you want the daemon application's VM to access. 113 | 1. Then click on **Access policies** menu and click on **+Add Access Policy**. 114 | 1. Select an adequate template from the dropdown "Configure from template" (ie "Secret & Certificate Management") or set the permissions manually (this sample requires the permission **GET** for Secret and Certificate to be checked). 115 | 1. For **Select principal**, search for the Azure VM *name* or *ObjectId*, select it and click on **Select** button. 116 | 1. Click on **Add**. 117 | 1. Then, **Save**. 118 | 119 | For more information about Key Vault, take a look at these links: 120 | 121 | - [Key Vault documentation](https://docs.microsoft.com/azure/key-vault/) 122 | - [Managed Identity Key Vault sample for dotnet](https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet) 123 | 124 | For more information about AzureVM and Managed Identities for Azure Resources, take a look at these links: 125 | 126 | - [Managed Identity documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) 127 | - [AzureVM documentation](https://azure.microsoft.com/services/virtual-machines/) 128 | 129 | For more information about the underlying protocol: 130 | 131 | - [Microsoft identity platform and the OAuth 2.0 client credentials flow](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow) 132 | 133 | For a more complex multi-tenant Web app daemon application, see [active-directory-dotnet-daemon-v2](https://github.com/Azure-Samples/active-directory-dotnet-daemon-v2) 134 | -------------------------------------------------------------------------------- /3-Using-KeyVault/ReadmeFiles/topology-certificates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/3-Using-KeyVault/ReadmeFiles/topology-certificates.png -------------------------------------------------------------------------------- /3-Using-KeyVault/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/3-Using-KeyVault/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/AppCreationScripts-withCert/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 | .\AppCreationScripts\Configure.ps1 15 | ``` 16 | 1. Open the Visual Studio solution and click start 17 | 18 | ### More details 19 | 20 | The following paragraphs: 21 | 22 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 23 | - Explain the [pre-requisites](#pre-requisites) 24 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 25 | - [Interactively](#option-1-interactive) to create the app in your home tenant 26 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 27 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 28 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 29 | 30 | ## Goal of the scripts 31 | 32 | ### Presentation of the scripts 33 | 34 | 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. 35 | 36 | These scripts are: 37 | 38 | - `Configure.ps1` which: 39 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets), 40 | - changes the configuration files in the C# and JavaScript projects. 41 | - 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: 42 | - the identifier of the application 43 | - the AppId of the application 44 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 45 | 46 | - `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). 47 | 48 | ### Usage pattern for tests and DevOps scenarios 49 | 50 | 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. 51 | 52 | ## How to use the app creation scripts ? 53 | 54 | ### Pre-requisites 55 | 56 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 57 | 2. Navigate to the root directory of the project. 58 | 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: 59 | ```PowerShell 60 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 61 | ``` 62 | ### (Optionally) install AzureAD PowerShell modules 63 | 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: 64 | 65 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 66 | 67 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 68 | 2. Type: 69 | ```PowerShell 70 | Install-Module AzureAD 71 | ``` 72 | 73 | or if you cannot be administrator on your machine, run: 74 | ```PowerShell 75 | Install-Module AzureAD -Scope CurrentUser 76 | ``` 77 | 78 | ### Run the script and start running 79 | 80 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 81 | ```PowerShell 82 | cd AppCreationScripts 83 | ``` 84 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 85 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 86 | 8. select **Start** for the projects 87 | 88 | You're done. this just works! 89 | 90 | ### Four ways to run the script 91 | 92 | We advise four ways of running the script: 93 | 94 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 95 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 96 | - 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, 97 | - 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. 98 | 99 | Here are the details on how to do this. 100 | 101 | #### Option 1 (interactive) 102 | 103 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 104 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 105 | 106 | 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. 107 | 108 | #### Option 2 (non-interactive) 109 | 110 | When you know the indentity 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 111 | 112 | ```PowerShell 113 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 114 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 115 | . .\Cleanup.ps1 -Credential $mycreds 116 | . .\Configure.ps1 -Credential $mycreds 117 | ``` 118 | 119 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 120 | 121 | #### Option 3 (Interactive, but create apps in a specified tenant) 122 | 123 | if you want to create the apps in a particular tenant, you can use the following option: 124 | - open the [Microsoft Entra admin center](https://entra.microsoft.com) 125 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 126 | - Find the "Active Directory" object in this tenant 127 | - Go to **Properties** and copy the content of the **Directory Id** property 128 | - Then use the full syntax to run the scripts: 129 | 130 | ```PowerShell 131 | $tenantId = "yourTenantIdGuid" 132 | . .\Cleanup.ps1 -TenantId $tenantId 133 | . .\Configure.ps1 -TenantId $tenantId 134 | ``` 135 | 136 | #### Option 4 (non-interactive, and create apps in a specified tenant) 137 | 138 | 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: 139 | 140 | ```PowerShell 141 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 142 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 143 | $tenantId = "yourTenantIdGuid" 144 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 145 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 146 | ``` 147 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/AppCreationScripts-withCert/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 'service' (TodoList-webapi-daemon-v2) if needed" 53 | Get-AzureADApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 54 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-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 TodoList-webapi-daemon-v2.." 64 | } 65 | # also remove service principals of this app 66 | Get-AzureADServicePrincipal -filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 67 | 68 | Write-Host "Removing 'client' (daemon-console-v2) if needed" 69 | Get-AzureADApplication -Filter "DisplayName eq 'daemon-console-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 70 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'daemon-console-v2'" 71 | if ($apps) 72 | { 73 | Remove-AzureADApplication -ObjectId $apps.ObjectId 74 | } 75 | 76 | foreach ($app in $apps) 77 | { 78 | Remove-AzureADApplication -ObjectId $app.ObjectId 79 | Write-Host "Removed daemon-console-v2.." 80 | } 81 | # also remove service principals of this app 82 | Get-AzureADServicePrincipal -filter "DisplayName eq 'daemon-console-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 83 | 84 | # remove self-signed certificate 85 | Get-ChildItem -Path Cert:\CurrentUser\My | where { $_.subject -eq "CN=DaemonConsoleCert" } | Remove-Item 86 | } 87 | 88 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/AppCreationScripts-withCert/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "A .NET Core daemon console application calling a custom Web API with its own identity", 4 | "Level": 200, 5 | "Client": ".NET Core (Console)", 6 | "Service": ".NET Core Web API", 7 | "RepositoryUrl": "active-directory-dotnetcore-daemon-v2", 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": "service", 17 | "Name": "TodoList-webapi-daemon-v2", 18 | "Kind": "WebApi", 19 | "Audience": "AzureADMyOrg", 20 | "HomePage": "https://localhost:44372", 21 | "AppRoles": [ 22 | { 23 | "Types" : ["Application"], 24 | "Name" : "DaemonAppRole", 25 | "Description" : "Daemon apps in this role can consume the web api." 26 | } 27 | ] 28 | 29 | }, 30 | { 31 | "Id": "client", 32 | "Name": "daemon-console-v2", 33 | "Kind": "Daemon", 34 | "Audience": "AzureADMyOrg", 35 | "PasswordCredentials": "Auto", 36 | "UsesROPCOrIWA": false, 37 | "ReplyUrls": "https://daemon", 38 | "Certificate": "CN=DaemonConsoleCert", 39 | "RequiredResourcesAccess": [ 40 | { 41 | "Resource": "service", 42 | "ApplicationPermissions": [ "DaemonAppRole" ] 43 | } 44 | ], 45 | "ManualSteps": [ 46 | { 47 | "Comment" : "Navigate to the API permissions page and click on 'Grant admin consent for {tenant}'" 48 | } 49 | ] 50 | } 51 | ], 52 | 53 | /* 54 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 55 | are created in Azure AD. 56 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 57 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 58 | */ 59 | "CodeConfiguration": [ 60 | { 61 | "App": "service", 62 | "SettingKind": "Text", 63 | "SettingFile": "\\..\\TodoList-WebApi\\appsettings.json", 64 | "Mappings": [ 65 | { 66 | "key": "Domain", 67 | "value": "$tenantName" 68 | }, 69 | { 70 | "key": "TenantId", 71 | "value": "$tenantId" 72 | }, 73 | { 74 | "key": "ClientId", 75 | "value": "service.AppId" 76 | } 77 | ] 78 | }, 79 | { 80 | "App": "client", 81 | "SettingKind": "JSon", 82 | "SettingFile": "\\..\\Daemon-Console\\appsettings.json", 83 | "Mappings": [ 84 | { 85 | "key": "Tenant", 86 | "value": "$tenantName" 87 | }, 88 | { 89 | "key": "ClientId", 90 | "value": ".AppId" 91 | }, 92 | { 93 | "key": "CertificateName", 94 | "value": ".Certificate" 95 | }, 96 | { 97 | "key": "TodoListScope", 98 | "value": "service.ScopeDefault" 99 | }, 100 | { 101 | "key": "TodoListBaseAddress", 102 | "value": "service.HomePage" 103 | } 104 | ] 105 | } 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/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 | .\AppCreationScripts\Configure.ps1 15 | ``` 16 | 1. Open the Visual Studio solution and click start 17 | 18 | ### More details 19 | 20 | The following paragraphs: 21 | 22 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 23 | - Explain the [pre-requisites](#pre-requisites) 24 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 25 | - [Interactively](#option-1-interactive) to create the app in your home tenant 26 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 27 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 28 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 29 | 30 | ## Goal of the scripts 31 | 32 | ### Presentation of the scripts 33 | 34 | 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. 35 | 36 | These scripts are: 37 | 38 | - `Configure.ps1` which: 39 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets), 40 | - changes the configuration files in the C# and JavaScript projects. 41 | - 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: 42 | - the identifier of the application 43 | - the AppId of the application 44 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 45 | 46 | - `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). 47 | 48 | ### Usage pattern for tests and DevOps scenarios 49 | 50 | 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. 51 | 52 | ## How to use the app creation scripts ? 53 | 54 | ### Pre-requisites 55 | 56 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 57 | 2. Navigate to the root directory of the project. 58 | 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: 59 | ```PowerShell 60 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 61 | ``` 62 | ### (Optionally) install AzureAD PowerShell modules 63 | 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: 64 | 65 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 66 | 67 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 68 | 2. Type: 69 | ```PowerShell 70 | Install-Module AzureAD 71 | ``` 72 | 73 | or if you cannot be administrator on your machine, run: 74 | ```PowerShell 75 | Install-Module AzureAD -Scope CurrentUser 76 | ``` 77 | 78 | ### Run the script and start running 79 | 80 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 81 | ```PowerShell 82 | cd AppCreationScripts 83 | ``` 84 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 85 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 86 | 8. select **Start** for the projects 87 | 88 | You're done. this just works! 89 | 90 | ### Four ways to run the script 91 | 92 | We advise four ways of running the script: 93 | 94 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 95 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 96 | - 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, 97 | - 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. 98 | 99 | Here are the details on how to do this. 100 | 101 | #### Option 1 (interactive) 102 | 103 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 104 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 105 | 106 | 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. 107 | 108 | #### Option 2 (non-interactive) 109 | 110 | When you know the indentity 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 111 | 112 | ```PowerShell 113 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 114 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 115 | . .\Cleanup.ps1 -Credential $mycreds 116 | . .\Configure.ps1 -Credential $mycreds 117 | ``` 118 | 119 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 120 | 121 | #### Option 3 (Interactive, but create apps in a specified tenant) 122 | 123 | if you want to create the apps in a particular tenant, you can use the following option: 124 | - open the [Microsoft Entra admin center](https://entra.microsoft.com) 125 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 126 | - Find the "Active Directory" object in this tenant 127 | - Go to **Properties** and copy the content of the **Directory Id** property 128 | - Then use the full syntax to run the scripts: 129 | 130 | ```PowerShell 131 | $tenantId = "yourTenantIdGuid" 132 | . .\Cleanup.ps1 -TenantId $tenantId 133 | . .\Configure.ps1 -TenantId $tenantId 134 | ``` 135 | 136 | #### Option 4 (non-interactive, and create apps in a specified tenant) 137 | 138 | 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: 139 | 140 | ```PowerShell 141 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 142 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 143 | $tenantId = "yourTenantIdGuid" 144 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 145 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 146 | ``` 147 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/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 'service' (TodoList-webapi-daemon-v2) if needed" 53 | Get-AzureADApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 54 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-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 TodoList-webapi-daemon-v2.." 64 | } 65 | # also remove service principals of this app 66 | Get-AzureADServicePrincipal -filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 67 | 68 | Write-Host "Removing 'client' (daemon-console-v2) if needed" 69 | Get-AzureADApplication -Filter "DisplayName eq 'daemon-console-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 70 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'daemon-console-v2'" 71 | if ($apps) 72 | { 73 | Remove-AzureADApplication -ObjectId $apps.ObjectId 74 | } 75 | 76 | foreach ($app in $apps) 77 | { 78 | Remove-AzureADApplication -ObjectId $app.ObjectId 79 | Write-Host "Removed daemon-console-v2.." 80 | } 81 | # also remove service principals of this app 82 | Get-AzureADServicePrincipal -filter "DisplayName eq 'daemon-console-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 83 | 84 | } 85 | 86 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "A .NET Core daemon console application calling a custom Web API with its own identity", 4 | "Level": 200, 5 | "Client": ".NET Core (Console)", 6 | "Service": ".NET Core Web API", 7 | "RepositoryUrl": "active-directory-dotnetcore-daemon-v2", 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": "service", 17 | "Name": "TodoList-webapi-daemon-v2", 18 | "Kind": "WebApi", 19 | "Audience": "AzureADMyOrg", 20 | "HomePage": "https://localhost:44372", 21 | "AppRoles": [ 22 | { 23 | "Types" : ["Application"], 24 | "Name" : "DaemonAppRole", 25 | "Description" : "Daemon apps in this role can consume the web api." 26 | } 27 | ] 28 | }, 29 | { 30 | "Id": "client", 31 | "Name": "daemon-console-v2", 32 | "Kind": "Daemon", 33 | "Audience": "AzureADMyOrg", 34 | "PasswordCredentials": "Auto", 35 | "UsesROPCOrIWA": false, 36 | "ReplyUrls": "https://daemon", 37 | "RequiredResourcesAccess": [ 38 | { 39 | "Resource": "service", 40 | "ApplicationPermissions": [ "DaemonAppRole" ] 41 | } 42 | ], 43 | "ManualSteps": [ 44 | { 45 | "Comment" : "Navigate to the API permissions page and click on 'Grant admin consent for {tenant}'" 46 | } 47 | ] 48 | } 49 | ], 50 | 51 | /* 52 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 53 | are created in Azure AD. 54 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 55 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 56 | */ 57 | "CodeConfiguration": [ 58 | { 59 | "App": "service", 60 | "SettingKind": "Text", 61 | "SettingFile": "\\..\\TodoList-WebApi\\appsettings.json", 62 | "Mappings": [ 63 | { 64 | "key": "Domain", 65 | "value": "$tenantName" 66 | }, 67 | { 68 | "key": "TenantId", 69 | "value": "$tenantId" 70 | }, 71 | { 72 | "key": "ClientId", 73 | "value": "service.AppId" 74 | } 75 | ] 76 | }, 77 | { 78 | "App": "client", 79 | "SettingKind": "JSon", 80 | "SettingFile": "\\..\\Daemon-Console\\appsettings.json", 81 | "Mappings": [ 82 | { 83 | "key": "Tenant", 84 | "value": "$tenantName" 85 | }, 86 | { 87 | "key": "ClientId", 88 | "value": ".AppId" 89 | }, 90 | { 91 | "key": "ClientSecret", 92 | "value": ".AppKey" 93 | }, 94 | { 95 | "key": "TodoListScope", 96 | "value": "service.ScopeDefault" 97 | }, 98 | { 99 | "key": "TodoListBaseAddress", 100 | "value": "service.HomePage" 101 | } 102 | ] 103 | } 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/IssuerConfigurationRetriever.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.IdentityModel.Protocols; 8 | using Newtonsoft.Json; 9 | 10 | namespace Microsoft.Identity.Web.Future 11 | { 12 | /// 13 | /// An implementation of IConfigurationRetriever geared towards Azure AD issuers metadata /> 14 | /// 15 | internal class IssuerConfigurationRetriever : IConfigurationRetriever 16 | { 17 | /// Retrieves a populated configuration given an address and an . 18 | /// Address of the discovery document. 19 | /// The to use to read the discovery document. 20 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. . 21 | /// 22 | /// address - Azure AD Issuer metadata address url is required 23 | /// or 24 | /// retriever - No metadata document retriever is provided 25 | public async Task GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel) 26 | { 27 | if (string.IsNullOrEmpty(address)) 28 | throw new ArgumentNullException(nameof(address), $"Azure AD Issuer metadata address url is required"); 29 | 30 | if (retriever == null) 31 | throw new ArgumentNullException(nameof(retriever), $"No metadata document retriever is provided"); 32 | 33 | string doc = await retriever.GetDocumentAsync(address, cancel).ConfigureAwait(false); 34 | return JsonConvert.DeserializeObject(doc); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/IssuerMetadata.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using Newtonsoft.Json; 6 | 7 | namespace Microsoft.Identity.Web.Future 8 | { 9 | /// 10 | /// Model class to hold information parsed from the Azure AD issuer endpoint 11 | /// 12 | internal class IssuerMetadata 13 | { 14 | /// 15 | /// Tenant discovery endpoint 16 | /// 17 | [JsonProperty(PropertyName = "tenant_discovery_endpoint")] 18 | public string TenantDiscoveryEndpoint { get; set; } 19 | 20 | /// 21 | /// API Version 22 | /// 23 | [JsonProperty(PropertyName = "api-version")] 24 | public string ApiVersion { get; set; } 25 | 26 | /// 27 | /// List of metadata associated with the endpoint 28 | /// 29 | [JsonProperty(PropertyName = "metadata")] 30 | public List Metadata { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/Metadata.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using Newtonsoft.Json; 6 | 7 | namespace Microsoft.Identity.Web.Future 8 | { 9 | /// 10 | /// Model child class to hold alias information parsed from the Azure AD issuer endpoint. 11 | /// 12 | internal class Metadata 13 | { 14 | /// 15 | /// Preferred alias 16 | /// 17 | [JsonProperty(PropertyName = "preferred_network")] 18 | public string PreferredNetwork { get; set; } 19 | 20 | /// 21 | /// Preferred alias to cache tokens emitted by one of the aliases (to avoid 22 | /// SSO islands) 23 | /// 24 | [JsonProperty(PropertyName = "preferred_cache")] 25 | public string PreferredCache { get; set; } 26 | 27 | /// 28 | /// Aliases of issuer URLs which are equivalent 29 | /// 30 | [JsonProperty(PropertyName = "aliases")] 31 | public List Aliases { get; set; } 32 | } 33 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/Microsoft.Identity.Web.Future.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | 5 | 6 | 3.0.0-preview 7 | 8 | $(ClientSemVer) 9 | 10 | $(DefineConstants);WEB 11 | true 12 | Microsoft 13 | Microsoft 14 | This package enables ASP.NET Core Web apps and Web APIs to use the Microsoft identity platform (formerly Azure AD v2.0). When they call Web APIs, MSAL.NET is used to acquire tokens 15 | © Microsoft Corporation. All rights reserved. 16 | MIT 17 | https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet 18 | https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet 19 | Microsoft Authentication Library MSAL Azure Active Directory AAD Identity .NET ASP.NET Core 20 | 21 | 22 | 23 | true 24 | true 25 | 26 | true 27 | snupkg 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/Microsoft.Identity.Web.Future.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29911.84 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.Future", "Microsoft.Identity.Web.Future.csproj", "{11CCC8C7-5E85-457D-ADD4-AA03E40A9AAC}" 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 | {11CCC8C7-5E85-457D-ADD4-AA03E40A9AAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {11CCC8C7-5E85-457D-ADD4-AA03E40A9AAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {11CCC8C7-5E85-457D-ADD4-AA03E40A9AAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {11CCC8C7-5E85-457D-ADD4-AA03E40A9AAC}.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 = {E7B6C459-1C2D-49FB-9B76-E94D243D586A} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/Microsoft.Identity.Web.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/Events/SignedHttpRequestAuthenticationFailedContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Http; 7 | 8 | namespace Microsoft.Identity.Web.SignedHttpRequest.Events 9 | { 10 | public class SignedHttpRequestAuthenticationFailedContext : ResultContext 11 | { 12 | public SignedHttpRequestAuthenticationFailedContext( 13 | HttpContext context, 14 | AuthenticationScheme scheme, 15 | SignedHttpRequestOptions options) 16 | : base(context, scheme, options) { } 17 | 18 | public Exception Exception { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/Events/SignedHttpRequestEvents.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Identity.Web.SignedHttpRequest.Events 8 | { 9 | public class SignedHttpRequestEvents 10 | { 11 | /// 12 | /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. 13 | /// 14 | public Func OnAuthenticationFailed { get; set; } = context => Task.CompletedTask; 15 | 16 | /// 17 | /// Invoked when a protocol message is first received. 18 | /// 19 | public Func OnMessageReceived { get; set; } = context => Task.CompletedTask; 20 | 21 | /// 22 | /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. 23 | /// 24 | public Func OnTokenValidated { get; set; } = context => Task.CompletedTask; 25 | 26 | public virtual Task AuthenticationFailed(SignedHttpRequestAuthenticationFailedContext context) => OnAuthenticationFailed(context); 27 | 28 | public virtual Task MessageReceived(SignedHttpRequestMessageReceivedContext context) => OnMessageReceived(context); 29 | 30 | public virtual Task TokenValidated(SignedHttpRequestValidatedContext context) => OnTokenValidated(context); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/Events/SignedHttpRequestMessageReceivedContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace Microsoft.Identity.Web.SignedHttpRequest.Events 8 | { 9 | public class SignedHttpRequestMessageReceivedContext : ResultContext 10 | { 11 | public SignedHttpRequestMessageReceivedContext( 12 | HttpContext context, 13 | AuthenticationScheme scheme, 14 | SignedHttpRequestOptions options) 15 | : base(context, scheme, options) { } 16 | 17 | /// 18 | /// PoP token. This will give the application an opportunity to retrieve a token from an alternative location. 19 | /// 20 | public string Token { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/Events/SignedHttpRequestValidatedContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.IdentityModel.Protocols.SignedHttpRequest; 7 | using Microsoft.IdentityModel.Tokens; 8 | 9 | namespace Microsoft.Identity.Web.SignedHttpRequest.Events 10 | { 11 | public class SignedHttpRequestValidatedContext : ResultContext 12 | { 13 | public SignedHttpRequestValidatedContext( 14 | HttpContext context, 15 | AuthenticationScheme scheme, 16 | SignedHttpRequestOptions options) 17 | : base(context, scheme, options) { } 18 | 19 | public SecurityToken SecurityToken { get; set; } 20 | 21 | public SignedHttpRequestValidationResult SignedHttpRequestValidationResult { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/SignedHttpRequestDefaults.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace Microsoft.Identity.Web.SignedHttpRequest 5 | { 6 | public class SignedHttpRequestDefaults 7 | { 8 | /// 9 | /// Default value for AuthenticationScheme property. 10 | /// 11 | public const string AuthenticationScheme = "PoP"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/SignedHttpRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace Microsoft.Identity.Web.SignedHttpRequest 11 | 12 | { 13 | public static class SignedHttpRequestExtensions 14 | { 15 | public static AuthenticationBuilder AddSignedHttpRequest(this AuthenticationBuilder builder) 16 | => builder.AddSignedHttpRequest(_ => { }); 17 | 18 | public static AuthenticationBuilder AddSignedHttpRequest(this AuthenticationBuilder builder, Action configureOptions) 19 | => builder.AddSignedHttpRequest(SignedHttpRequestDefaults.AuthenticationScheme, configureOptions); 20 | 21 | public static AuthenticationBuilder AddSignedHttpRequest(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) 22 | => builder.AddSignedHttpRequest(authenticationScheme, displayName: null, configureOptions: configureOptions); 23 | 24 | public static AuthenticationBuilder AddSignedHttpRequest(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) 25 | { 26 | builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, SignedHttpRequestPostConfigureOptions>()); 27 | return builder.AddScheme(authenticationScheme, displayName, configureOptions); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/SignedHttpRequestLoggingExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Microsoft.Identity.Web.SignedHttpRequest 8 | { 9 | internal static class SignedHttpRequestLoggingExtensions 10 | { 11 | private static Action _tokenValidationFailed; 12 | private static Action _tokenValidationSucceeded; 13 | private static Action _errorProcessingMessage; 14 | 15 | static SignedHttpRequestLoggingExtensions() 16 | { 17 | _tokenValidationFailed = LoggerMessage.Define( 18 | eventId: 1, 19 | logLevel: LogLevel.Information, 20 | formatString: "Failed to validate the token."); 21 | _tokenValidationSucceeded = LoggerMessage.Define( 22 | eventId: 2, 23 | logLevel: LogLevel.Information, 24 | formatString: "Successfully validated the token."); 25 | _errorProcessingMessage = LoggerMessage.Define( 26 | eventId: 3, 27 | logLevel: LogLevel.Error, 28 | formatString: "Exception occurred while processing message."); 29 | } 30 | 31 | public static void TokenValidationFailed(this ILogger logger, Exception ex) 32 | => _tokenValidationFailed(logger, ex); 33 | 34 | public static void TokenValidationSucceeded(this ILogger logger) 35 | => _tokenValidationSucceeded(logger, null); 36 | 37 | public static void ErrorProcessingMessage(this ILogger logger, Exception ex) 38 | => _errorProcessingMessage(logger, ex); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/SignedHttpRequestOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Net.Http; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.Identity.Web.SignedHttpRequest.Events; 8 | using Microsoft.IdentityModel.Protocols; 9 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 10 | using Microsoft.IdentityModel.Protocols.SignedHttpRequest; 11 | using Microsoft.IdentityModel.Tokens; 12 | 13 | namespace Microsoft.Identity.Web.SignedHttpRequest 14 | { 15 | public class SignedHttpRequestOptions : AuthenticationSchemeOptions 16 | { 17 | /// 18 | /// Gets or sets if HTTPS is required for the metadata address or authority. 19 | /// The default is true. This should be disabled only in development environments. 20 | /// 21 | public bool RequireHttpsMetadata { get; set; } = true; 22 | 23 | /// 24 | /// Gets or sets the discovery endpoint for obtaining metadata 25 | /// 26 | public string MetadataAddress { get; set; } 27 | 28 | /// 29 | /// Gets or sets the Authority to use when making OpenIdConnect calls. 30 | /// 31 | public string Authority { get; set; } 32 | 33 | public string Instance { get; set; } 34 | 35 | public string Domain { get; set; } 36 | 37 | /// 38 | /// Gets or sets a single valid audience value for any received OpenIdConnect token. 39 | /// This value is passed into TokenValidationParameters.ValidAudience if that property is empty. 40 | /// 41 | /// 42 | /// The expected audience for any received OpenIdConnect token. 43 | /// 44 | public string ClientId { get; set; } 45 | 46 | /// 47 | /// The object provided by the application to process events raised by the bearer authentication handler. 48 | /// The application may implement the interface fully, or it may create an instance of JwtBearerEvents 49 | /// and assign delegates only to the events it wants to process. 50 | /// 51 | public new SignedHttpRequestEvents Events 52 | { 53 | get { return (SignedHttpRequestEvents)base.Events; } 54 | set { base.Events = value; } 55 | } 56 | 57 | /// 58 | /// The HttpMessageHandler used to retrieve metadata. 59 | /// This cannot be set at the same time as BackchannelCertificateValidator unless the value 60 | /// is a WebRequestHandler. 61 | /// 62 | public HttpMessageHandler BackchannelHttpHandler { get; set; } 63 | 64 | /// 65 | /// Gets or sets the timeout when using the backchannel to make an http call. 66 | /// 67 | public TimeSpan BackchannelTimeout { get; set; } = TimeSpan.FromMinutes(1); 68 | 69 | /// 70 | /// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties 71 | /// will not be used. This information should not be updated during request processing. 72 | /// 73 | public OpenIdConnectConfiguration Configuration { get; set; } 74 | 75 | /// 76 | /// Responsible for retrieving, caching, and refreshing the configuration from metadata. 77 | /// If not provided, then one will be created using the MetadataAddress and Backchannel properties. 78 | /// 79 | public IConfigurationManager ConfigurationManager { get; set; } 80 | 81 | /// 82 | /// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic 83 | /// recovery in the event of a signature key rollover. This is enabled by default. 84 | /// 85 | public bool RefreshOnIssuerKeyNotFound { get; set; } = true; 86 | 87 | /// 88 | /// Gets or sets the parameters used to validate the access token inside the signed http request. 89 | /// 90 | /// Contains the types and definitions required for validating a token. 91 | /// if 'value' is null. 92 | public TokenValidationParameters AccessTokenValidationParameters { get; set; } = new TokenValidationParameters(); 93 | 94 | public SignedHttpRequestValidationParameters SignedHttpRequestValidationParameters { get; set; } = new SignedHttpRequestValidationParameters(); 95 | 96 | /// 97 | /// 1 day is the default time interval that afterwards, will obtain new configuration. 98 | /// 99 | public TimeSpan AutomaticRefreshInterval { get; set; } = ConfigurationManager.DefaultAutomaticRefreshInterval; 100 | 101 | /// 102 | /// The minimum time between retrievals, in the event that a retrieval failed, or that a refresh was explicitly requested. 30 seconds is the default. 103 | /// 104 | public TimeSpan RefreshInterval { get; set; } = ConfigurationManager.DefaultRefreshInterval; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/SignedHttpRequest/SignedHttpRequestPostConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Net.Http; 6 | using Microsoft.Extensions.Options; 7 | using Microsoft.IdentityModel.Protocols; 8 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 9 | 10 | namespace Microsoft.Identity.Web.SignedHttpRequest 11 | { 12 | /// 13 | /// Used to setup defaults for all . 14 | /// 15 | public class SignedHttpRequestPostConfigureOptions : IPostConfigureOptions 16 | { 17 | /// 18 | /// Invoked to post configure a JwtBearerOptions instance. 19 | /// 20 | /// The name of the options instance being configured. 21 | /// The options instance to configure. 22 | public void PostConfigure(string name, SignedHttpRequestOptions options) 23 | { 24 | if (string.IsNullOrEmpty(options.AccessTokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(options.ClientId)) 25 | { 26 | options.AccessTokenValidationParameters.ValidAudience = options.ClientId; 27 | } 28 | 29 | if (options.ConfigurationManager == null) 30 | { 31 | if (options.Configuration != null) 32 | { 33 | options.ConfigurationManager = new StaticConfigurationManager(options.Configuration); 34 | } 35 | else if (!(string.IsNullOrEmpty(options.MetadataAddress) && string.IsNullOrEmpty(options.Authority))) 36 | { 37 | if (string.IsNullOrEmpty(options.MetadataAddress) && !string.IsNullOrEmpty(options.Authority)) 38 | { 39 | options.MetadataAddress = options.Authority; 40 | if (!options.MetadataAddress.EndsWith("/", StringComparison.Ordinal)) 41 | { 42 | options.MetadataAddress += "/"; 43 | } 44 | 45 | options.MetadataAddress += ".well-known/openid-configuration"; 46 | } 47 | 48 | if (options.RequireHttpsMetadata && !options.MetadataAddress.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) 49 | { 50 | throw new InvalidOperationException("The MetadataAddress or Authority must use HTTPS unless disabled for development by setting RequireHttpsMetadata=false."); 51 | } 52 | 53 | var httpClient = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler()); 54 | httpClient.Timeout = options.BackchannelTimeout; 55 | httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB 56 | 57 | options.ConfigurationManager = new ConfigurationManager(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(), 58 | new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata }) 59 | { 60 | RefreshInterval = options.RefreshInterval, 61 | AutomaticRefreshInterval = options.AutomaticRefreshInterval, 62 | }; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/Microsoft.Identity.Web.Future/WebApiServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Identity.Future; 7 | using Microsoft.Identity.Web.SignedHttpRequest; 8 | 9 | namespace Microsoft.Identity.Web 10 | { 11 | /// 12 | /// Extensions for IServiceCollection for startup initialization of Web APIs. 13 | /// 14 | public static class WebApiServiceCollectionExtensions 15 | { 16 | public static IServiceCollection AddProofOfPossession( 17 | this IServiceCollection services, 18 | IConfiguration configuration, 19 | string configSectionName = "AzureAd") 20 | { 21 | services.AddAuthentication(SignedHttpRequestDefaults.AuthenticationScheme) 22 | .AddSignedHttpRequest(options => configuration.Bind(configSectionName, options)); 23 | services.Configure(options => configuration.Bind(configSectionName, options)); 24 | 25 | services.AddHttpContextAccessor(); 26 | 27 | // Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0). 28 | services.Configure(SignedHttpRequestDefaults.AuthenticationScheme, options => 29 | { 30 | options.Authority = options.Instance + options.Domain; 31 | 32 | // This is an Microsoft identity platform Web API 33 | options.Authority += "/v2.0"; 34 | 35 | // The valid audiences are both the Client ID (options.Audience) and api://{ClientID} 36 | options.AccessTokenValidationParameters.ValidAudiences = new string[] 37 | { 38 | options.ClientId, $"api://{options.ClientId}" 39 | }; 40 | 41 | // Instead of using the default validation (validating against a single tenant, as we do in line of business apps), 42 | // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens) 43 | options.AccessTokenValidationParameters.IssuerValidator = AadIssuerValidator.GetIssuerValidator(options.Authority).Validate; 44 | 45 | options.Events ??= new SignedHttpRequest.Events.SignedHttpRequestEvents(); 46 | var onAuthenticationFailed = options.Events.OnAuthenticationFailed; 47 | options.Events.OnAuthenticationFailed = async context => 48 | { 49 | await onAuthenticationFailed(context).ConfigureAwait(false); 50 | }; 51 | var onMessageReceivedHandler = options.Events.OnMessageReceived; 52 | options.Events.OnMessageReceived = async context => 53 | { 54 | await onMessageReceivedHandler(context).ConfigureAwait(false); 55 | }; 56 | 57 | var tokenvalidationHandler = options.Events.OnTokenValidated; 58 | options.Events.OnTokenValidated = async context => 59 | { 60 | 61 | context.HttpContext.Items["JwtSecurityTokenUsedToCallWebAPI"] = context.SecurityToken; 62 | await tokenvalidationHandler(context).ConfigureAwait(false); 63 | }; 64 | }); 65 | 66 | return services; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/README.md: -------------------------------------------------------------------------------- 1 | 2 | # IMPORTANT: PLEASE READ 3 | # This sample corresponds to an implementation of PoP without server nonce that is no longer recommended. If you start new projects don’t use this sample. The recommended approach will be documented in the future. 4 | 5 | Please use the sample [A \.NET Core daemon console application calling a protected Web API with its own identity](https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/2-Call-OwnApi) to address the authentication flow supported by this sample 6 | 7 | For information about Pop, see [Proof of Possession](https://learn.microsoft.com/entra/msal/dotnet/advanced/proof-of-possession-tokens?view=msal-dotnet-latest) 8 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/ReadmeFiles/topology-certificates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/4-Call-OwnApi-Pop/ReadmeFiles/topology-certificates.png -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/d0f8548d91a146e950d36dc0f4981898817e447f/4-Call-OwnApi-Pop/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/Controllers/TodoListController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Identity.Web.Resource; 9 | using Newtonsoft.Json; 10 | using TodoList_WebApi.Models; 11 | 12 | namespace TodoList_WebApi.Controllers 13 | { 14 | [Authorize] 15 | [Route("api/[controller]")] 16 | [ApiController] 17 | public class TodoListController : ControllerBase 18 | { 19 | // In-memory TodoList 20 | private static readonly Dictionary TodoStore = new Dictionary(); 21 | 22 | public TodoListController() 23 | { 24 | // Pre-populate with sample data 25 | if (TodoStore.Count == 0) 26 | { 27 | TodoStore.Add(1, new TodoItem() { Id = 1, Task = "Pick up groceries" }); 28 | TodoStore.Add(2, new TodoItem() { Id = 2, Task = "Finish invoice report" }); 29 | TodoStore.Add(3, new TodoItem() { Id = 3, Task = "Water plants" }); 30 | } 31 | } 32 | 33 | // GET: api/todolist 34 | [HttpGet] 35 | public IActionResult Get() 36 | { 37 | HttpContext.ValidateAppRole("DaemonAppRole"); 38 | return Ok(TodoStore.Values); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/Models/TodoItem.cs: -------------------------------------------------------------------------------- 1 | namespace TodoList_WebApi.Models 2 | { 3 | public class TodoItem 4 | { 5 | public int Id { get; set; } 6 | public string Task { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace TodoList_WebApi 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "https://localhost:44372", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "TodoList-WebApi": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:44372" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets.user" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Authentication.JwtBearer; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Identity.Web; 11 | 12 | namespace TodoList_WebApi 13 | { 14 | public class Startup 15 | { 16 | public Startup(IConfiguration configuration) 17 | { 18 | Configuration = configuration; 19 | } 20 | 21 | public IConfiguration Configuration { get; } 22 | 23 | // This method gets called by the runtime. Use this method to add services to the container. 24 | public void ConfigureServices(IServiceCollection services) 25 | { 26 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 27 | .AddMicrosoftIdentityWebApi(Configuration); 28 | services.AddProofOfPossession(Configuration); 29 | 30 | services.AddControllers(); 31 | } 32 | 33 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 34 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 35 | { 36 | if (env.IsDevelopment()) 37 | { 38 | // Since IdentityModel version 5.2.1 (or since Microsoft.AspNetCore.Authentication.JwtBearer version 2.2.0), 39 | // Personal Identifiable Information is not written to the logs by default, to be compliant with GDPR. 40 | // For debugging/development purposes, one can enable additional detail in exceptions by setting IdentityModelEventSource.ShowPII to true. 41 | // Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | else 45 | { 46 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 47 | app.UseHsts(); 48 | } 49 | 50 | app.UseHttpsRedirection(); 51 | 52 | app.UseRouting(); 53 | app.UseAuthentication(); 54 | app.UseAuthorization(); 55 | 56 | app.UseEndpoints(endpoints => 57 | { 58 | endpoints.MapControllers(); 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/TodoList-WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | InProcess 6 | TodoList_WebApi 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/TodoList-WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AzureAd": { 3 | "Instance": "https://login.microsoftonline.com/", 4 | "ClientId": "8206b76f-586e-4098-b1e5-598c1aa3e2a1", 5 | "Domain": "msidentitysamplestesting.onmicrosoft.com", 6 | "TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab" 7 | }, 8 | "Logging": { 9 | "LogLevel": { 10 | "Default": "Warning" 11 | } 12 | }, 13 | "AllowedHosts": "*" 14 | } 15 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/daemon-console.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daemon-Console", "daemon-console\Daemon-Console.csproj", "{DE92BA34-DBCA-4087-BBAB-85AC833031BA}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{241AB638-4786-435A-8565-93AD3C40BBB4}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList-WebApi", "TodoList-WebApi\TodoList-WebApi.csproj", "{78B0FEAF-64DF-483B-A653-BF3110053FA3}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.Future", "Microsoft.Identity.Web.Future\Microsoft.Identity.Web.Future.csproj", "{13512A33-57B0-4825-9232-FD61DE922421}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {13512A33-57B0-4825-9232-FD61DE922421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {13512A33-57B0-4825-9232-FD61DE922421}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {13512A33-57B0-4825-9232-FD61DE922421}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {13512A33-57B0-4825-9232-FD61DE922421}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {5A9A2601-8CEB-4475-9E95-43A5438B71CD} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/daemon-console/AuthenticationConfig.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Microsoft Corporation 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | using Microsoft.Extensions.Configuration; 26 | using System; 27 | using System.Globalization; 28 | using System.IO; 29 | 30 | namespace daemon_console 31 | { 32 | /// 33 | /// Description of the configuration of an AzureAD public client application (desktop/mobile application). This should 34 | /// match the application registration done in the Azure portal 35 | /// 36 | public class AuthenticationConfig 37 | { 38 | /// 39 | /// instance of Azure AD, for example public Azure or a Sovereign cloud (Azure China, Germany, US government, etc ...) 40 | /// 41 | public string Instance { get; set; } = "https://login.microsoftonline.com/{0}"; 42 | 43 | /// 44 | /// The Tenant is: 45 | /// - either the tenant ID of the Azure AD tenant in which this application is registered (a guid) 46 | /// or a domain name associated with the tenant 47 | /// - or 'organizations' (for a multi-tenant application) 48 | /// 49 | public string Tenant { get; set; } 50 | 51 | /// 52 | /// Guid used by the application to uniquely identify itself to Azure AD 53 | /// 54 | public string ClientId { get; set; } 55 | 56 | /// 57 | /// URL of the authority 58 | /// 59 | public string Authority 60 | { 61 | get 62 | { 63 | return String.Format(CultureInfo.InvariantCulture, Instance, Tenant); 64 | } 65 | } 66 | 67 | /// 68 | /// Client secret (application password) 69 | /// 70 | /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret 71 | /// (which is a kind of application password: this property) 72 | /// or a certificate previously shared with AzureAD during the application registration 73 | /// (and identified by the CertificateName property belows) 74 | /// 75 | public string ClientSecret { get; set; } 76 | 77 | /// 78 | /// Name of a certificate in the user certificate store 79 | /// 80 | /// Daemon applications can authenticate with AAD through two mechanisms: ClientSecret 81 | /// (which is a kind of application password: the property above) 82 | /// or a certificate previously shared with AzureAD during the application registration 83 | /// (and identified by this CertificateName property) 84 | /// 85 | public string CertificateName { get; set; } 86 | 87 | /// 88 | /// Web Api base URL 89 | /// 90 | public string TodoListBaseAddress { get; set; } 91 | 92 | /// 93 | /// Web Api scope. With client credentials flows, the scopes is ALWAYS of the shape "resource/.default" 94 | /// 95 | public string TodoListScope { get; set; } 96 | 97 | /// 98 | /// Reads the configuration from a json file 99 | /// 100 | /// Path to the configuration json file 101 | /// AuthenticationConfig read from the json file 102 | public static AuthenticationConfig ReadFromJsonFile(string path) 103 | { 104 | IConfigurationRoot Configuration; 105 | 106 | var builder = new ConfigurationBuilder() 107 | .SetBasePath(Directory.GetCurrentDirectory()) 108 | .AddJsonFile(path); 109 | 110 | Configuration = builder.Build(); 111 | return Configuration.Get(); 112 | } 113 | } 114 | 115 | 116 | 117 | } 118 | 119 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/daemon-console/Daemon-Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | daemon_console 7 | 8 | 9 | 10 | TRACE 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/daemon-console/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Identity.Client; 5 | using Microsoft.Identity.Client.AppConfig; 6 | using Microsoft.Identity.Web; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Net.Http; 11 | using System.Security.Cryptography.X509Certificates; //Only import this if you are using certificate 12 | using System.Text.Json.Nodes; 13 | using System.Threading.Tasks; 14 | 15 | namespace daemon_console 16 | { 17 | /// 18 | /// This sample shows how to query the Microsoft Graph from a daemon application 19 | /// which uses application permissions. 20 | /// For more information see https://aka.ms/msal-net-client-credentials 21 | /// 22 | class Program 23 | { 24 | static void Main(string[] args) 25 | { 26 | try 27 | { 28 | RunAsync().GetAwaiter().GetResult(); 29 | } 30 | catch (Exception ex) 31 | { 32 | Console.ForegroundColor = ConsoleColor.Red; 33 | Console.WriteLine(ex.Message); 34 | Console.ResetColor(); 35 | } 36 | 37 | Console.WriteLine("Press any key to exit"); 38 | Console.ReadKey(); 39 | } 40 | 41 | private static async Task RunAsync() 42 | { 43 | AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); 44 | 45 | // You can run this sample using ClientSecret or Certificate. The code will differ only when instantiating the IConfidentialClientApplication 46 | bool isUsingClientSecret = AppUsesClientSecret(config); 47 | 48 | // Even if this is a console application here, a daemon application is a confidential client application 49 | IConfidentialClientApplication app; 50 | 51 | if (isUsingClientSecret) 52 | { 53 | app = ConfidentialClientApplicationBuilder.Create(config.ClientId) 54 | .WithClientSecret(config.ClientSecret) 55 | .WithAuthority(new Uri(config.Authority)) 56 | .WithExperimentalFeatures() // for PoP 57 | .Build(); 58 | } 59 | 60 | else 61 | { 62 | X509Certificate2 certificate = ReadCertificate(config.CertificateName); 63 | app = ConfidentialClientApplicationBuilder.Create(config.ClientId) 64 | .WithCertificate(certificate) 65 | .WithAuthority(new Uri(config.Authority)) 66 | .WithExperimentalFeatures() 67 | .Build(); 68 | } 69 | 70 | app.AddInMemoryTokenCache(); 71 | 72 | // With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the 73 | // application permissions need to be set statically (in the portal or by PowerShell), and then granted by 74 | // a tenant administrator 75 | string[] scopes = new string[] { config.TodoListScope }; 76 | 77 | AuthenticationResult result = null; 78 | string popUri = $"{config.TodoListBaseAddress}/api/todolist"; 79 | try 80 | { 81 | result = await app.AcquireTokenForClient(scopes) 82 | .WithProofOfPossession(new PoPAuthenticationConfiguration(new Uri(popUri)) { HttpMethod = HttpMethod.Get }) 83 | .ExecuteAsync(); 84 | Console.ForegroundColor = ConsoleColor.Green; 85 | Console.WriteLine("Token acquired \n"); 86 | Console.ResetColor(); 87 | } 88 | catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011")) 89 | { 90 | // Invalid scope. The scope has to be of the form "https://resourceurl/.default" 91 | // Mitigation: change the scope to be as expected 92 | Console.ForegroundColor = ConsoleColor.Red; 93 | Console.WriteLine("Scope provided is not supported"); 94 | Console.ResetColor(); 95 | } 96 | 97 | if (result != null) 98 | { 99 | var httpClient = new HttpClient(); 100 | var apiCaller = new ProtectedApiCallHelper(httpClient); 101 | await apiCaller.CallWebApiAndProcessResultASync(popUri, result, Display); 102 | } 103 | } 104 | 105 | /// 106 | /// Display the result of the Web API call 107 | /// 108 | /// Object to display 109 | private static void Display(JsonNode result) 110 | { 111 | Console.WriteLine("Web Api result: \n"); 112 | 113 | JsonArray nodes = result.AsArray(); 114 | 115 | foreach (JsonObject aNode in nodes.ToArray()) 116 | { 117 | foreach (var property in aNode.ToArray()) 118 | { 119 | Console.WriteLine($"{property.Key} = {property.Value?.ToString()}"); 120 | } 121 | Console.WriteLine(); 122 | } 123 | 124 | } 125 | 126 | /// 127 | /// Checks if the sample is configured for using ClientSecret or Certificate. This method is just for the sake of this sample. 128 | /// You won't need this verification in your production application since you will be authenticating in AAD using one mechanism only. 129 | /// 130 | /// Configuration from appsettings.json 131 | /// 132 | private static bool AppUsesClientSecret(AuthenticationConfig config) 133 | { 134 | string clientSecretPlaceholderValue = "[Enter here a client secret for your application]"; 135 | string certificatePlaceholderValue = "[Or instead of client secret: Enter here the name of a certificate (from the user cert store) as registered with your application]"; 136 | 137 | if (!string.IsNullOrWhiteSpace(config.ClientSecret) && config.ClientSecret != clientSecretPlaceholderValue) 138 | { 139 | return true; 140 | } 141 | 142 | else if (!string.IsNullOrWhiteSpace(config.CertificateName) && config.CertificateName != certificatePlaceholderValue) 143 | { 144 | return false; 145 | } 146 | 147 | else 148 | throw new Exception("You must choose between using client secret or certificate. Please update appsettings.json file."); 149 | } 150 | 151 | private static X509Certificate2 ReadCertificate(string certificateName) 152 | { 153 | if (string.IsNullOrWhiteSpace(certificateName)) 154 | { 155 | throw new ArgumentException("certificateName should not be empty. Please set the CertificateName setting in the appsettings.json", "certificateName"); 156 | } 157 | X509Certificate2 cert = null; 158 | 159 | using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) 160 | { 161 | store.Open(OpenFlags.ReadOnly); 162 | X509Certificate2Collection certCollection = store.Certificates; 163 | 164 | // Find unexpired certificates. 165 | X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); 166 | 167 | // From the collection of unexpired certificates, find the ones with the correct name. 168 | X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, certificateName, false); 169 | 170 | // Return the first certificate in the collection, has the right name and is current. 171 | cert = signingCert.OfType().OrderByDescending(c => c.NotBefore).FirstOrDefault(); 172 | } 173 | return cert; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/daemon-console/ProtectedApiCallHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Identity.Client; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Net.Http.Headers; 10 | using System.Text.Json.Nodes; 11 | using System.Threading.Tasks; 12 | 13 | namespace daemon_console 14 | { 15 | /// 16 | /// Helper class to call a protected API and process its result 17 | /// 18 | public class ProtectedApiCallHelper 19 | { 20 | /// 21 | /// Constructor 22 | /// 23 | /// HttpClient used to call the protected API 24 | public ProtectedApiCallHelper(HttpClient httpClient) 25 | { 26 | HttpClient = httpClient; 27 | } 28 | 29 | protected HttpClient HttpClient { get; private set; } 30 | 31 | 32 | /// 33 | /// Calls the protected web API and processes the result 34 | /// 35 | /// URL of the web API to call (supposed to return Json) 36 | /// AuthenticationResult returned as a result of the call to the web API. 37 | /// Callback used to process the result of the call to the web API. 38 | public async Task CallWebApiAndProcessResultASync( 39 | string webApiUrl, 40 | AuthenticationResult result, 41 | Action processResult) 42 | { 43 | if ( result != null) 44 | { 45 | var defaultRequestHeaders = HttpClient.DefaultRequestHeaders; 46 | if (defaultRequestHeaders.Accept == null || !defaultRequestHeaders.Accept.Any(m => m.MediaType == "application/json")) 47 | { 48 | HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 49 | } 50 | defaultRequestHeaders.Add("Authorization", result.CreateAuthorizationHeader()); 51 | 52 | HttpResponseMessage response = await HttpClient.GetAsync(webApiUrl); 53 | if (response.IsSuccessStatusCode) 54 | { 55 | string json = await response.Content.ReadAsStringAsync(); 56 | JsonNode apiResult = JsonNode.Parse(json); 57 | Console.ForegroundColor = ConsoleColor.Gray; 58 | processResult(apiResult); 59 | } 60 | else 61 | { 62 | Console.ForegroundColor = ConsoleColor.Red; 63 | Console.WriteLine($"Failed to call the Web Api: {response.StatusCode}"); 64 | string content = await response.Content.ReadAsStringAsync(); 65 | 66 | // Note that if you got reponse.Code == 403 and response.content.code == "Authorization_RequestDenied" 67 | // this is because the tenant admin as not granted consent for the application to call the Web API 68 | Console.WriteLine($"Content: {content}"); 69 | } 70 | Console.ResetColor(); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /4-Call-OwnApi-Pop/daemon-console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Instance": "https://login.microsoftonline.com/{0}", 3 | "Tenant": "msidentitysamplestesting.onmicrosoft.com", 4 | "ClientId": "1b4649ec-7a55-4145-9821-bf5efe85ffdb", 5 | "ClientSecret": "[Enter here a client secret for your application]", 6 | "CertificateName": "[Or instead of client secret: Enter here the name of a certificate (from the user cert store) as registered with your application]", 7 | "TodoListBaseAddress": "https://localhost:44372", 8 | "TodoListScope": "api://8206b76f-586e-4098-b1e5-598c1aa3e2a1/.default" 9 | } 10 | -------------------------------------------------------------------------------- /5-Call-MSGraph-ManagedIdentity/Daemon-Console.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34607.119 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daemon-Console", "daemon-console\Daemon-Console.csproj", "{2612866A-EB0D-4666-9E01-CB8F19A41761}" 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 | {2612866A-EB0D-4666-9E01-CB8F19A41761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2612866A-EB0D-4666-9E01-CB8F19A41761}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {2612866A-EB0D-4666-9E01-CB8F19A41761}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {2612866A-EB0D-4666-9E01-CB8F19A41761}.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 = {54DF6125-5DC4-4E84-A05A-B31BDD68DEF4} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /5-Call-MSGraph-ManagedIdentity/daemon-console/Daemon-Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | daemon_console 7 | 8 | 9 | 10 | TRACE 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /5-Call-MSGraph-ManagedIdentity/daemon-console/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Graph; 6 | using Microsoft.Identity.Web; 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | namespace daemon_console 11 | { 12 | /// 13 | /// This sample shows how to query the Microsoft Graph from a daemon application using a managed identity. 14 | /// 15 | class Program 16 | { 17 | static async Task Main(string[] _) 18 | { 19 | // Get the Token acquirer factory instance. By default it reads an appsettings.json 20 | // file if it exists in the same folder as the app (make sure that the 21 | // "Copy to Output Directory" property of the appsettings.json file is "Copy if newer"). 22 | TokenAcquirerFactory tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance(); 23 | 24 | // Configure the application options to be read from the configuration 25 | // and add the services you need (Graph, token cache) 26 | var services = tokenAcquirerFactory.Services; 27 | services.AddMicrosoftGraph(); 28 | // By default, you get an in-memory token cache. 29 | // For more token cache serialization options, see https://aka.ms/msal-net-token-cache-serialization 30 | 31 | // Resolve the dependency injection. 32 | var serviceProvider = tokenAcquirerFactory.Build(); 33 | 34 | // Call Microsoft Graph using the Graph SDK 35 | try 36 | { 37 | GraphServiceClient graphServiceClient = serviceProvider.GetRequiredService(); 38 | var users = await graphServiceClient.Users 39 | .GetAsync(r => r.Options.WithAppOnly() 40 | .WithAuthenticationOptions(o => 41 | { 42 | // Specify your target Microsoft Graph endpoint as the scope for the request. 43 | // A list of Microoft Graph endpoints can be found at https://docs.microsoft.com/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints 44 | o.Scopes = new string[] { "https://graph.microsoft.com/.default" }; 45 | // Tell the library to use a managed identity, by default it uses the system-assigned managed identity. 46 | o.AcquireTokenOptions.ManagedIdentity = new() 47 | { 48 | // Uncomment the below line and edit the value to use a user-assigned managed identity. 49 | // UserAssignedClientId = "ClientID-of-the-user-assigned-managed-identity" 50 | }; 51 | }) 52 | ); 53 | Console.WriteLine($"{users.Value.Count} users"); 54 | } 55 | catch (ServiceException e) 56 | { 57 | Console.WriteLine("We could not retrieve the user's list: " + $"{e}"); 58 | 59 | // If you get the following exception, here is what you need to do 60 | // --------------------------------------------------------------- 61 | // IDW10503: Cannot determine the cloud Instance. 62 | // Provide the configuration (appsettings.json with an "AzureAd" section, and "Instance" set, 63 | // the project needs to be this way) 64 | // 65 | // < None Update = "appsettings.json" > 66 | // < CopyToOutputDirectory > PreserveNewest 67 | // 68 | // 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering sample apps with the Microsoft identity platform and updating configuration files using PowerShell 2 | 3 | ## Overview 4 | 5 | ### Quick summary 6 | 7 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. 8 | 9 | ```PowerShell 10 | cd .\AppCreationScripts\ 11 | .\Configure.ps1 -TenantId "your test tenant's id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'" 12 | ``` 13 | 14 | ### More details 15 | 16 | - [Goal of the provided scripts](#goal-of-the-provided-scripts) 17 | - [Presentation of the scripts](#presentation-of-the-scripts) 18 | - [Usage pattern for tests and DevOps scenarios](#usage-pattern-for-tests-and-DevOps-scenarios) 19 | - [How to use the app creation scripts?](#how-to-use-the-app-creation-scripts) 20 | - [Pre-requisites](#pre-requisites) 21 | - [Run the script and start running](#run-the-script-and-start-running) 22 | - [Four ways to run the script](#four-ways-to-run-the-script) 23 | - [Option 1 (interactive)](#option-1-interactive) 24 | - [Option 2 (Interactive, but create apps in a specified tenant)](#option-3-Interactive-but-create-apps-in-a-specified-tenant) 25 | - [Running the script on Azure Sovereign clouds](#running-the-script-on-Azure-Sovereign-clouds) 26 | 27 | ## Goal of the provided scripts 28 | 29 | ### Presentation of the scripts 30 | 31 | 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. 32 | 33 | These scripts are: 34 | 35 | - `Configure.ps1` which: 36 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets, app roles), 37 | - changes the configuration files in the sample projects. 38 | - 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: 39 | - the identifier of the application 40 | - the AppId of the application 41 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com). 42 | 43 | - `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`). 44 | 45 | ### Usage pattern for tests and DevOps scenarios 46 | 47 | 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. 48 | 49 | ## How to use the app creation scripts? 50 | 51 | ### Pre-requisites 52 | 53 | 1. Powershell 7 or later 54 | 1. You can follow the instrunctions to install PowerShell at [this link](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3) 55 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 56 | 57 | ### (Optionally) install Microsoft.Graph.Applications PowerShell modules 58 | 59 | The scripts install the required PowerShell module (Microsoft.Graph.Applications) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: 60 | 61 | 1. If not done already, in the PowerShell window, install the Microsoft.Graph.Applications PowerShell modules. For this: 62 | 63 | 1. Open PowerShell 64 | 2. Type: 65 | 66 | ```PowerShell 67 | Install-Module Microsoft.Graph.Applications 68 | ``` 69 | 70 | or if you want the modules to be installed for the current user only, run: 71 | 72 | ```PowerShell 73 | Install-Module Microsoft.Graph.Applications -Scope CurrentUser 74 | ``` 75 | 76 | ### Run the script and start running 77 | 78 | 1. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 79 | 80 | ```PowerShell 81 | cd AppCreationScripts 82 | ``` 83 | 84 | 1. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 85 | 1. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 86 | 1. select **Start** for the projects 87 | 88 | You're done! 89 | 90 | ### Two ways to run the script 91 | 92 | We advise four ways of running the script: 93 | 94 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 95 | - 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, 96 | 97 | Here are the details on how to do this. 98 | 99 | #### Option 1 (interactive) 100 | 101 | - Just run ``.\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 102 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 103 | 104 | 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. 105 | 106 | #### Option 2 (Interactive, but create apps in a specified tenant) 107 | 108 | if you want to create the apps in a particular tenant, you can use the following option: 109 | 110 | - Open the [Microsoft Entra admin center](https://entra.microsoft.com) 111 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window) 112 | - Find the "Active Directory" object in this tenant 113 | - Go to **Properties** and copy the content of the **Directory Id** property 114 | - Then use the full syntax to run the scripts: 115 | 116 | ```PowerShell 117 | $tenantId = "yourTenantIdGuid" 118 | . .\Cleanup.ps1 -TenantId $tenantId 119 | . .\Configure.ps1 -TenantId $tenantId 120 | ``` 121 | 122 | ### Running the script on Azure Sovereign clouds 123 | 124 | 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`. 125 | 126 | The acceptable values for this parameter are: 127 | 128 | - AzureCloud 129 | - AzureChinaCloud 130 | - AzureUSGovernment 131 | 132 | Example: 133 | 134 | ```PowerShell 135 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureUSGovernment" 136 | . .\Configure.ps1 -AzureEnvironmentName "AzureUSGovernment" 137 | ``` 138 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/AppCreationScripts/Cleanup.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7 2 | 3 | [CmdletBinding()] 4 | param( 5 | [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')] 6 | [string] $tenantId, 7 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')] 8 | [string] $azureEnvironmentName 9 | ) 10 | 11 | 12 | Function Cleanup 13 | { 14 | if (!$azureEnvironmentName) 15 | { 16 | $azureEnvironmentName = "Global" 17 | } 18 | 19 | <# 20 | .Description 21 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script 22 | #> 23 | 24 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 25 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 26 | 27 | # Connect to the Microsoft Graph API 28 | Write-Host "Connecting to Microsoft Graph" 29 | 30 | 31 | if ($tenantId -eq "") 32 | { 33 | Connect-MgGraph -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 34 | } 35 | else 36 | { 37 | Connect-MgGraph -TenantId $tenantId -Scopes "User.Read.All Organization.Read.All Application.ReadWrite.All" -Environment $azureEnvironmentName 38 | } 39 | 40 | $context = Get-MgContext 41 | $tenantId = $context.TenantId 42 | 43 | # Get the user running the script 44 | $currentUserPrincipalName = $context.Account 45 | $user = Get-MgUser -Filter "UserPrincipalName eq '$($context.Account)'" 46 | 47 | # get the tenant we signed in to 48 | $Tenant = Get-MgOrganization 49 | $tenantName = $Tenant.DisplayName 50 | 51 | $verifiedDomain = $Tenant.VerifiedDomains | where {$_.Isdefault -eq $true} 52 | $verifiedDomainName = $verifiedDomain.Name 53 | $tenantId = $Tenant.Id 54 | 55 | Write-Host ("Connected to Tenant {0} ({1}) as account '{2}'. Domain is '{3}'" -f $Tenant.DisplayName, $Tenant.Id, $currentUserPrincipalName, $verifiedDomainName) 56 | 57 | # Removes the applications 58 | Write-Host "Cleaning-up applications from tenant '$tenantId'" 59 | 60 | Write-Host "Removing 'service' (TodoList-webapi-daemon-v2) if needed" 61 | try 62 | { 63 | Get-MgApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id } 64 | } 65 | catch 66 | { 67 | $message = $_ 68 | Write-Warning $Error[0] 69 | Write-Host "Unable to remove the application 'TodoList-webapi-daemon-v2'. Error is $message. Try deleting manually." -ForegroundColor White -BackgroundColor Red 70 | } 71 | 72 | Write-Host "Making sure there are no more (TodoList-webapi-daemon-v2) applications found, will remove if needed..." 73 | $apps = Get-MgApplication -Filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | Format-List Id, DisplayName, AppId, SignInAudience, PublisherDomain 74 | 75 | if ($apps) 76 | { 77 | Remove-MgApplication -ApplicationId $apps.Id 78 | } 79 | 80 | foreach ($app in $apps) 81 | { 82 | Remove-MgApplication -ApplicationId $app.Id 83 | Write-Host "Removed TodoList-webapi-daemon-v2.." 84 | } 85 | 86 | # also remove service principals of this app 87 | try 88 | { 89 | Get-MgServicePrincipal -filter "DisplayName eq 'TodoList-webapi-daemon-v2'" | ForEach-Object {Remove-MgServicePrincipal -ServicePrincipalId $_.Id -Confirm:$false} 90 | } 91 | catch 92 | { 93 | $message = $_ 94 | Write-Warning $Error[0] 95 | Write-Host "Unable to remove ServicePrincipal 'TodoList-webapi-daemon-v2'. Error is $message. Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red 96 | } 97 | } 98 | 99 | # Pre-requisites 100 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph")) { 101 | Install-Module "Microsoft.Graph" -Scope CurrentUser 102 | } 103 | 104 | #Import-Module Microsoft.Graph 105 | 106 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Authentication")) { 107 | Install-Module "Microsoft.Graph.Authentication" -Scope CurrentUser 108 | } 109 | 110 | Import-Module Microsoft.Graph.Authentication 111 | 112 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Identity.DirectoryManagement")) { 113 | Install-Module "Microsoft.Graph.Identity.DirectoryManagement" -Scope CurrentUser 114 | } 115 | 116 | Import-Module Microsoft.Graph.Identity.DirectoryManagement 117 | 118 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) { 119 | Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser 120 | } 121 | 122 | Import-Module Microsoft.Graph.Applications 123 | 124 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Groups")) { 125 | Install-Module "Microsoft.Graph.Groups" -Scope CurrentUser 126 | } 127 | 128 | Import-Module Microsoft.Graph.Groups 129 | 130 | if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Users")) { 131 | Install-Module "Microsoft.Graph.Users" -Scope CurrentUser 132 | } 133 | 134 | Import-Module Microsoft.Graph.Users 135 | 136 | $ErrorActionPreference = "Stop" 137 | 138 | 139 | try 140 | { 141 | Cleanup -tenantId $tenantId -environment $azureEnvironmentName 142 | } 143 | catch 144 | { 145 | $_.Exception.ToString() | out-host 146 | $message = $_ 147 | Write-Warning $Error[0] 148 | Write-Host "Unable to register apps. Error is $message." -ForegroundColor White -BackgroundColor Red 149 | } 150 | 151 | Write-Host "Disconnecting from tenant" 152 | Disconnect-MgGraph 153 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "A .NET Core daemon console application calling a custom Web API with its own identity", 4 | "Level": 200, 5 | "Client": ".NET Core (Console)", 6 | "Service": ".NET Core Web API", 7 | "RepositoryUrl": "active-directory-dotnetcore-daemon-v2", 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": "service", 17 | "Name": "TodoList-webapi-daemon-v2", 18 | "Kind": "WebApi", 19 | "Audience": "AzureADMyOrg", 20 | "HomePage": "https://localhost:44372", 21 | "SDK": "MicrosoftIdentityWeb", 22 | "SampleSubPath": "2-Call-OwnApi\\TodoList-WebApi", 23 | "AppRoles": [ 24 | { 25 | "Types" : ["Application"], 26 | "Name" : "DaemonAppRole", 27 | "Description" : "Daemon apps in this role can consume the web api." 28 | } 29 | ] 30 | }, 31 | { 32 | "Id": "client", 33 | "Name": "daemon-console-v2", 34 | "Kind": "Daemon", 35 | "Audience": "AzureADMyOrg", 36 | "PasswordCredentials": "Auto", 37 | "UsesROPCOrIWA": false, 38 | "SDK": "MicrosoftIdentityWeb", 39 | "SampleSubPath": "2-Call-OwnApi\\daemon-console", 40 | "ReplyUrls": "https://daemon", 41 | "RequiredResourcesAccess": [ 42 | { 43 | "Resource": "service", 44 | "ApplicationPermissions": [ "DaemonAppRole" ] 45 | } 46 | ], 47 | "ManualSteps": [ 48 | { 49 | "Comment" : "Navigate to the API permissions page and click on 'Grant admin consent for {tenant}'" 50 | } 51 | ] 52 | } 53 | ], 54 | 55 | /* 56 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 57 | are created in Azure AD. 58 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 59 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 60 | */ 61 | "CodeConfiguration": [ 62 | { 63 | "App": "service", 64 | "SettingKind": "Text", 65 | "SettingFile": "\\..\\TodoList-WebApi\\appsettings.json", 66 | "Mappings": [ 67 | { 68 | "key": "Domain", 69 | "value": "$tenantName" 70 | }, 71 | { 72 | "key": "TenantId", 73 | "value": "$tenantId" 74 | }, 75 | { 76 | "key": "ClientId", 77 | "value": "service.AppId" 78 | } 79 | ] 80 | }, 81 | { 82 | "App": "client", 83 | "SettingKind": "JSon", 84 | "SettingFile": "\\..\\Daemon-Console\\appsettings.json", 85 | "Mappings": [ 86 | { 87 | "key": "Tenant", 88 | "value": "$tenantName" 89 | }, 90 | { 91 | "key": "ClientId", 92 | "value": ".AppId" 93 | }, 94 | { 95 | "key": "ClientSecret`\":", 96 | "value": ".AppKey" 97 | }, 98 | { 99 | "key": "TodoListScope", 100 | "value": "service.ScopeDefault" 101 | }, 102 | { 103 | "key": "TodoListBaseAddress", 104 | "value": "service.HomePage" 105 | } 106 | ] 107 | }, 108 | { 109 | "App": "client", 110 | "SettingKind": "Replace", 111 | "SettingFile": "\\..\\Daemon-Console\\appsettings.json", 112 | "Mappings": [ 113 | { 114 | "key": "[Enter here the scopes for your web API]", 115 | "value": "\"api://\"+$serviceAadApplication.AppId+\"/.default\"" 116 | } 117 | ] 118 | } 119 | ] 120 | } 121 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/Controllers/TodoListController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Authorization; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.Identity.Web.Resource; 12 | using TodoList_WebApi.Models; 13 | 14 | namespace TodoList_WebApi.Controllers 15 | { 16 | [Authorize] 17 | [Route("api/[controller]")] 18 | [ApiController] 19 | public class TodoListController : ControllerBase 20 | { 21 | // In-memory TodoList 22 | private static readonly Dictionary TodoStore = new Dictionary(); 23 | 24 | public TodoListController() 25 | { 26 | // Pre-populate with sample data 27 | if (TodoStore.Count == 0) 28 | { 29 | TodoStore.Add(1, new TodoItem() { Id = 1, Task = "Pick up groceries" }); 30 | TodoStore.Add(2, new TodoItem() { Id = 2, Task = "Finish invoice report" }); 31 | TodoStore.Add(3, new TodoItem() { Id = 3, Task = "Water plants" }); 32 | TodoStore.Add(4, new TodoItem() { Id = 3, Task = "Water plants" }); 33 | } 34 | } 35 | 36 | // GET: api/todolist 37 | [HttpGet] 38 | [RequiredScopeOrAppPermission(AcceptedAppPermission = new[] { "DaemonAppRole" })] 39 | public IActionResult Get() 40 | { 41 | return Ok(TodoStore.Values); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/Models/TodoItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace TodoList_WebApi.Models 5 | { 6 | public class TodoItem 7 | { 8 | public int Id { get; set; } 9 | public string Task { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace TodoList_WebApi 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | CreateHostBuilder(args).Build().Run(); 14 | } 15 | 16 | public static IHostBuilder CreateHostBuilder(string[] args) => 17 | Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder.UseStartup(); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "https://localhost:44372", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "TodoList-WebApi": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:44372" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "secrets1": { 4 | "type": "secrets.user" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Authentication.JwtBearer; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Identity.Web; 11 | using System.IdentityModel.Tokens.Jwt; 12 | 13 | namespace TodoList_WebApi 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 28 | .AddMicrosoftIdentityWebApi(Configuration); 29 | 30 | services.AddControllers(); 31 | } 32 | 33 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 34 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 35 | { 36 | if (env.IsDevelopment()) 37 | { 38 | // Since IdentityModel version 5.2.1 (or since Microsoft.AspNetCore.Authentication.JwtBearer version 2.2.0), 39 | // Personal Identifiable Information is not written to the logs by default, to be compliant with GDPR. 40 | // For debugging/development purposes, one can enable additional detail in exceptions by setting IdentityModelEventSource.ShowPII to true. 41 | // Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | else 45 | { 46 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 47 | app.UseHsts(); 48 | } 49 | 50 | app.UseHttpsRedirection(); 51 | 52 | app.UseRouting(); 53 | app.UseAuthentication(); 54 | app.UseAuthorization(); 55 | 56 | app.UseEndpoints(endpoints => 57 | { 58 | endpoints.MapControllers(); 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/TodoList-WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | InProcess 6 | TodoList_WebApi 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/TodoList-WebApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AzureAd": { 3 | "Instance": "https://login.microsoftonline.com/", 4 | "TenantId": "tenant-guid", 5 | "ClientId": "web-api-application-guid", 6 | "Scopes": "DaemonAppRole", 7 | "EnablePiiLogging": false 8 | }, 9 | "Logging": { 10 | "LogLevel": { 11 | "Default": "Information", 12 | "Microsoft.Identity": "Warning", 13 | "Microsoft.AspNetCore": "Warning" 14 | } 15 | }, 16 | "AllowedHosts": "*" 17 | } 18 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/daemon-console-calls-api-msi.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daemon-Console", "daemon-console\Daemon-Console.csproj", "{DE92BA34-DBCA-4087-BBAB-85AC833031BA}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{241AB638-4786-435A-8565-93AD3C40BBB4}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList-WebApi", "TodoList-WebApi\TodoList-WebApi.csproj", "{78B0FEAF-64DF-483B-A653-BF3110053FA3}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {DE92BA34-DBCA-4087-BBAB-85AC833031BA}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {78B0FEAF-64DF-483B-A653-BF3110053FA3}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {5A9A2601-8CEB-4475-9E95-43A5438B71CD} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/daemon-console/Daemon-Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | daemon_console 7 | 8 | 9 | 10 | TRACE 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/daemon-console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Identity.Abstractions; 3 | using Microsoft.Identity.Web; 4 | using System.Collections.Generic; 5 | using System; 6 | using System.Linq; 7 | using TodoList_WebApi.Models; 8 | using Microsoft.Extensions.Logging; 9 | 10 | // Get the Token acquirer factory instance. By default it reads an appsettings.json 11 | // file if it exists in the same folder as the app (make sure that the 12 | // "Copy to Output Directory" property of the appsettings.json file is "Copy if newer"). 13 | var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance(); 14 | 15 | // Add console logging or other services if you wish 16 | tokenAcquirerFactory.Services.AddLogging( 17 | (loggingBuilder) => loggingBuilder.SetMinimumLevel(LogLevel.Warning) 18 | .AddConsole() 19 | ); 20 | 21 | // Create a downstream API service named 'MyApi' which comes loaded with several 22 | // utility methods to make HTTP calls to the DownstreamApi configurations found 23 | // in the "MyWebApi" section of your appsettings.json file. 24 | tokenAcquirerFactory.Services.AddDownstreamApi("MyApi", 25 | tokenAcquirerFactory.Configuration.GetSection("MyWebApi")); 26 | var sp = tokenAcquirerFactory.Build(); 27 | 28 | // Extract the downstream API service from the 'tokenAcquirerFactory' service provider. 29 | var api = sp.GetRequiredService(); 30 | 31 | // You can use the API service to make direct HTTP calls to your API. Token 32 | // acquisition is handled automatically based on the configurations in your 33 | // appsettings.json file. 34 | var result = await api.GetForAppAsync>("MyApi"); 35 | Console.WriteLine($"result = {result?.Count()}"); 36 | -------------------------------------------------------------------------------- /6-Call-OwnApi-ManagedIdentity/daemon-console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "MyWebApi": { 3 | "BaseUrl": "https://localhost:44372/", 4 | "RelativePath": "api/TodoList", 5 | "RequestAppToken": true, 6 | "Scopes": [ "api://web-api-application-guid/.default" ], 7 | "AcquireTokenOptions": { 8 | "ManagedIdentity": { 9 | "UserAssignedClientId ": "user-assigned-managed-identity-client-id" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 6 | 7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 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 | - microsoft-entra-id 8 | description: "Daemon or unattended application consuming Microsoft Graph or your own Web Apis using Microsoft Identity Platform to acquire tokens." 9 | urlFragment: ms-identity-daemon 10 | --- 11 | 12 | # A .NET Core daemon console application using MSAL.NET to acquire tokens for resources 13 | 14 | [![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/aad%20Samples/.NET%20client%20samples/active-directory-dotnetcore-daemon-v2%20CI)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=695) 15 | 16 | ## About this sample 17 | 18 | > This sample uses Microsoft.Identity.Web, which is a higher level API on top of MSAL.NET. If you are interested in the raw MSAL.NET code, see [this archived branch](https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/withMsal) 19 | 20 | ### Scenario 21 | 22 | In these scenarios we show how unattended daemon applications can authenticate as itself using the Microsoft Authentication Library for .NET ([MSAL.NET](https://aka.ms/msal-net)) SDK and acquire [Access Tokens](https://aka.ms/access-tokens) for various web APIs like [Microsoft Graph](https://aka.ms/msgraph) or any other API secured with the [Microsoft Identity Platform](https://aka.ms/identityplatform) 23 | 24 | ### Structure of the repository 25 | 26 | This repository contains a chapter-wise tutorial made of three parts: 27 | 28 | Sub folder | Description 29 | ----------------------------- | ----------- 30 | [1-Call-Graph](https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/1-Call-MSGraph) | This sample application shows how to use the Microsoft Authentication Library for .NET ([MSAL.NET](https://aka.ms/msal-net)) to access the data of Microsoft business customers in a long-running, non-interactive process. It uses the [OAuth 2 client credentials grant](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow)to acquire an access token, which can be used to call the [Microsoft Graph](https://aka.ms/msgraph) and access organizational data

![Topology](./1-Call-MSGraph/ReadmeFiles/topology.png) 31 | [2-Call-OwnApi](https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/2-Call-OwnApi) | This sample application shows how to use the Microsoft Authentication Library for .NET ([MSAL.NET](https://aka.ms/msal-net)) to access the data from a protected Web API, in a non-interactive process. It uses the [OAuth 2 client credentials grant](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow) to acquire an access token, which is then used to call the Web API. Additionally, it lays down all the steps developers need to take to secure their Web APIs with the Microsoft identity platform.

![Topology](./2-Call-OwnApi/ReadmeFiles/topology.png) 32 | [3-Using-KeyVault](https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/3-Using-KeyVault) | This chapter explains how to integrate [Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/basic-concepts) and [Managed Identities for Azure Resources](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) with a daemon application. Additionally, it has a code snippet on how to get an access token for Key Vault. 33 | 34 | ## How to run this sample 35 | 36 | To run this sample, you'll need: 37 | 38 | - [Visual Studio](https://aka.ms/vsdownload) and the [.NET Core SDK](https://www.microsoft.com/net/learn/get-started) 39 | - An Internet connection 40 | - A Windows machine (necessary if you want to run the app on Windows) 41 | - An OS X machine (necessary if you want to run the app on Mac) 42 | - A Linux machine (necessary if you want to run the app on Linux) 43 | - 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/documentation/articles/active-directory-howto-tenant/) 44 | - A user account in your Microsoft Entra tenant. This sample will not work with a Microsoft account (formerly Windows Live account). Therefore, if you signed in to the [Microsoft Entra admin center](https://entra.microsoft.com) with a Microsoft account and have never created a user account in your directory before, you need to do that now. 45 | 46 | ### Step 1: Clone or download this repository 47 | 48 | From your shell or command line: 49 | 50 | ```Shell 51 | git clone https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2.git 52 | ``` 53 | 54 | or download and exact the repository .zip file. 55 | 56 | > Given that the name of the sample is pretty long, 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. 57 | 58 | ## Community Help and Support 59 | 60 | Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. 61 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. 62 | Make sure that your questions or comments are tagged with [`msal` `dotnet`]. 63 | 64 | If you find a bug in the sample, please raise the issue on [GitHub Issues](https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/issues). 65 | 66 | If you find a bug in msal.Net, please raise the issue on [MSAL.NET GitHub Issues](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues). 67 | 68 | To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory). 69 | 70 | > [Consider taking a moment to share your experience with us.](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbRy8G199fkJNDjJ9kJaxUJIhUNUJGSDU1UkxFMlRSWUxGVTlFVkpGT0tOTi4u) 71 | 72 | ## Contributing 73 | 74 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/blob/master/CONTRIBUTING.md). 75 | 76 | 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. 77 | 78 | ## More information 79 | 80 | For more information, see MSAL.NET's conceptual documentation: 81 | 82 | - [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) 83 | - [Quickstart: Configure a client application to access web APIs](https://docs.microsoft.com/azure/active-directory/develop/quickstart-configure-app-access-web-apis) 84 | - [Acquiring a token for an application with client credential flows](https://aka.ms/msal-net-client-credentials) 85 | 86 | For more information about the underlying protocol: 87 | 88 | - [Microsoft identity platform and the OAuth 2.0 client credentials flow](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow) 89 | 90 | For a more complex multi-tenant Web app daemon application, see [active-directory-dotnet-daemon-v2](https://github.com/Azure-Samples/active-directory-dotnet-daemon-v2) 91 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | msbuild /t:restore buildAllSlns.proj 2 | msbuild buildAllSlns.proj -------------------------------------------------------------------------------- /buildAllSlns.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------