├── .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 CopyToOutputDirectory >
56 | // None >
57 | // ItemGroup >
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 CopyToOutputDirectory >
67 | // None >
68 | // ItemGroup >
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 | [](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

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. 
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 |
--------------------------------------------------------------------------------