├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── dotnetcore.yml
├── .gitignore
├── 1-Calling-MSGraph
├── 1-1-AzureAD
│ ├── AppCreationScripts
│ │ ├── AppCreationScripts.md
│ │ ├── Cleanup.ps1
│ │ ├── Configure.ps1
│ │ └── sample.json
│ ├── Console-Interactive-MultiTarget
│ │ ├── Console-Interactive-MultiTarget.csproj
│ │ ├── Program.cs
│ │ └── appsettings.json
│ ├── Console-Interactive.sln
│ ├── README.md
│ └── ReadmeFiles
│ │ └── topology.png
├── 1-2-AzureB2C
│ └── Placeholder.md
└── 1-3-NationalClouds
│ ├── README.md
│ └── ReadmeFiles
│ └── topology.png
├── 2-TokenCache
├── AppCreationScripts
│ ├── AppCreationScripts.md
│ ├── Cleanup.ps1
│ ├── Configure.ps1
│ └── sample.json
├── Console-TokenCache.sln
├── Console-TokenCache
│ ├── CacheSettings.cs
│ ├── Console-TokenCache.csproj
│ ├── Program.cs
│ └── appsettings.json
└── README.md
├── 3-CustomWebUI
├── 3-1-CustomHTML
│ ├── AppCreationScripts
│ │ ├── AppCreationScripts.md
│ │ ├── Cleanup.ps1
│ │ ├── Configure.ps1
│ │ └── sample.json
│ ├── Console-Interactive-CustomWebUI
│ │ ├── Console-Interactive-CustomWebUI.csproj
│ │ ├── Program.cs
│ │ └── appsettings.json
│ ├── Console-Interactive.sln
│ ├── README.md
│ └── ReadmeFiles
│ │ ├── failureMessage.png
│ │ ├── successMessage.png
│ │ └── topology.png
└── 3-2-CustomBrowser
│ ├── AppCreationScripts
│ ├── AppCreationScripts.md
│ ├── Cleanup.ps1
│ ├── Configure.ps1
│ └── sample.json
│ ├── Console-Interactive-Core.sln
│ ├── Console-Interactive-CustomWebUI
│ ├── Console-Interactive-CustomWebUI.csproj
│ ├── CustomWebBrowser
│ │ ├── CustomBrowserWebUi.cs
│ │ └── SingleMessageTcpListener.cs
│ ├── Program.cs
│ └── appsettings.json
│ ├── README.md
│ └── ReadmeFiles
│ ├── failureMessage.png
│ ├── successMessage.png
│ └── topology.png
├── 4-DeviceCodeFlow
├── AppCreationScripts
│ ├── AppCreationScripts.md
│ ├── Cleanup.ps1
│ ├── Configure.ps1
│ └── sample.json
├── Console-DeviceCodeFlow-v2.sln
├── Console-DeviceCodeFlow-v2
│ ├── Console-DeviceCodeFlow-v2.csproj
│ ├── Program.cs
│ └── appsettings.json
├── Readme.md
└── ReadmeFiles
│ └── topology.png
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
└── SECURITY.md
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
4 | > Please provide us with the following information:
5 | > ---------------------------------------------------------------
6 |
7 | ### This issue is for a: (mark with an `x`)
8 | ```
9 | - [ ] bug report -> please search issues before submitting
10 | - [ ] feature request
11 | - [ ] documentation issue or request
12 | - [ ] regression (a behavior that used to work and stopped in a new release)
13 | ```
14 |
15 | ### Minimal steps to reproduce
16 | >
17 |
18 | ### Any log messages given by the failure
19 | >
20 |
21 | ### Expected/desired behavior
22 | >
23 |
24 | ### OS and Version?
25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?)
26 |
27 | ### Versions
28 | >
29 |
30 | ### Mention any other details that might be useful
31 |
32 | > ---------------------------------------------------------------
33 | > Thanks! We'll be in touch soon.
34 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Purpose
2 |
3 | * ...
4 |
5 | ## Does this introduce a breaking change?
6 |
7 | ```
8 | [ ] Yes
9 | [ ] No
10 | ```
11 |
12 | ## Pull Request Type
13 | What kind of change does this Pull Request introduce?
14 |
15 |
16 | ```
17 | [ ] Bugfix
18 | [ ] Feature
19 | [ ] Code style update (formatting, local variables)
20 | [ ] Refactoring (no functional changes, no api changes)
21 | [ ] Documentation content changes
22 | [ ] Other... Please describe:
23 | ```
24 |
25 | ## How to Test
26 | * Get the code
27 |
28 | ```
29 | git clone [repo-address]
30 | cd [repo-name]
31 | git checkout [branch-name]
32 | npm install
33 | ```
34 |
35 | * Test the code
36 |
37 | ```
38 | ```
39 |
40 | ## What to Check
41 | Verify that the following are valid
42 | * ...
43 |
44 | ## Other Information
45 |
46 |
--------------------------------------------------------------------------------
/.github/workflows/dotnetcore.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: windows-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Setup .NET Core
17 | uses: actions/setup-dotnet@v1
18 | with:
19 | dotnet-version: 3.1.101
20 | - name: Build 1-1-AzureAD
21 | run: dotnet build --configuration Release .\1-Calling-MSGraph\1-1-AzureAD\Console-Interactive.sln
22 | - name: Build 2-TokenCache
23 | run: dotnet build --configuration Release .\2-TokenCache\Console-TokenCache.sln
24 |
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/AppCreationScripts.md:
--------------------------------------------------------------------------------
1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts
2 |
3 | ## Overview
4 |
5 | ### Quick summary
6 |
7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory
8 | 1. In PowerShell run:
9 | ```PowerShell
10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
11 | ```
12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below)
13 | ```PowerShell
14 | cd .\AppCreationScripts\
15 | .\Configure.ps1
16 | ```
17 | 1. Open the Visual Studio solution and click start
18 |
19 | ### More details
20 |
21 | The following paragraphs:
22 |
23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios.
24 | - Explain the [pre-requisites](#pre-requisites)
25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script):
26 | - [Interactively](#option-1-interactive) to create the app in your home tenant
27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant
28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant)
29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant)
30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds)
31 |
32 | ## Goal of the scripts
33 |
34 | ### Presentation of the scripts
35 |
36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra applications, and the configuration of the code for this sample. Once you run them, you will only need to build the solution and you are good to test.
37 |
38 | These scripts are:
39 |
40 | - `Configure.ps1` which:
41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets),
42 | - changes the configuration files in the C# and JavaScript projects.
43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created:
44 | - the identifier of the application
45 | - the AppId of the application
46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com).
47 |
48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset).
49 |
50 | ### Usage pattern for tests and DevOps scenarios
51 |
52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below.
53 |
54 | ## How to use the app creation scripts?
55 |
56 | ### Pre-requisites
57 |
58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window)
59 | 2. Navigate to the root directory of the project.
60 | 3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command:
61 | ```PowerShell
62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
63 | ```
64 | ### (Optionally) install AzureAD PowerShell modules
65 | The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps:
66 |
67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
68 |
69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator).
70 | 2. Type:
71 | ```PowerShell
72 | Install-Module AzureAD
73 | ```
74 |
75 | or if you cannot be administrator on your machine, run:
76 | ```PowerShell
77 | Install-Module AzureAD -Scope CurrentUser
78 | ```
79 |
80 | ### Run the script and start running
81 |
82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo,
83 | ```PowerShell
84 | cd AppCreationScripts
85 | ```
86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that.
87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**.
88 | 8. select **Start** for the projects
89 |
90 | You're done. this just works!
91 |
92 | ### Four ways to run the script
93 |
94 | We advise four ways of running the script:
95 |
96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects,
97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects,
98 | - Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects,
99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects.
100 |
101 | Here are the details on how to do this.
102 |
103 | #### Option 1 (interactive)
104 |
105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA).
106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined.
107 |
108 | Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in.
109 |
110 | #### Option 2 (non-interactive)
111 |
112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window
113 |
114 | ```PowerShell
115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
117 | . .\Cleanup.ps1 -Credential $mycreds
118 | . .\Configure.ps1 -Credential $mycreds
119 | ```
120 |
121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault.
122 |
123 | #### Option 3 (Interactive, but create apps in a specified tenant)
124 |
125 | if you want to create the apps in a particular tenant, you can use the following option:
126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com)
127 | - Select the Microsoft Entra tenant you are interested in (in the combo-box below your name on the top right of the browser window)
128 | - Find the "Active Directory" object in this tenant
129 | - Go to **Properties** and copy the content of the **Directory Id** property
130 | - Then use the full syntax to run the scripts:
131 |
132 | ```PowerShell
133 | $tenantId = "yourTenantIdGuid"
134 | . .\Cleanup.ps1 -TenantId $tenantId
135 | . .\Configure.ps1 -TenantId $tenantId
136 | ```
137 |
138 | #### Option 4 (non-interactive, and create apps in a specified tenant)
139 |
140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run:
141 |
142 | ```PowerShell
143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
145 | $tenantId = "yourTenantIdGuid"
146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId
147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId
148 | ```
149 |
150 | ### Running the script on Azure Sovereign clouds
151 |
152 | All the four options listed above, can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`.
153 |
154 | The acceptable values for this parameter are:
155 |
156 | - AzureCloud
157 | - AzureChinaCloud
158 | - AzureUSGovernment
159 | - AzureGermanyCloud
160 |
161 | Example:
162 |
163 | ```PowerShell
164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud"
165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud"
166 | ```
167 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/Cleanup.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId
6 | )
7 |
8 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) {
9 | Install-Module "AzureAD" -Scope CurrentUser
10 | }
11 | Import-Module AzureAD
12 | $ErrorActionPreference = "Stop"
13 |
14 | Function Cleanup
15 | {
16 | <#
17 | .Description
18 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script
19 | #>
20 |
21 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
22 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
23 |
24 | # Login to Azure PowerShell (interactive if credentials are not already provided:
25 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
26 | if (!$Credential -and $TenantId)
27 | {
28 | $creds = Connect-AzureAD -TenantId $tenantId
29 | }
30 | else
31 | {
32 | if (!$TenantId)
33 | {
34 | $creds = Connect-AzureAD -Credential $Credential
35 | }
36 | else
37 | {
38 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential
39 | }
40 | }
41 |
42 | if (!$tenantId)
43 | {
44 | $tenantId = $creds.Tenant.Id
45 | }
46 | $tenant = Get-AzureADTenantDetail
47 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name
48 |
49 | # Removes the applications
50 | Write-Host "Cleaning-up applications from tenant '$tenantName'"
51 |
52 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed"
53 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId }
54 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'"
55 | if ($apps)
56 | {
57 | Remove-AzureADApplication -ObjectId $apps.ObjectId
58 | }
59 |
60 | foreach ($app in $apps)
61 | {
62 | Remove-AzureADApplication -ObjectId $app.ObjectId
63 | Write-Host "Removed Console-Interactive-MultiTarget-v2.."
64 | }
65 | # also remove service principals of this app
66 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false}
67 |
68 | }
69 |
70 | Cleanup -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/Configure.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId
6 | )
7 |
8 | <#
9 | This script creates the Azure AD applications needed for this sample and updates the configuration files
10 | for the visual Studio projects from the data in the Azure AD applications.
11 |
12 | Before running this script you need to install the AzureAD cmdlets as an administrator.
13 | For this:
14 | 1) Run Powershell as an administrator
15 | 2) in the PowerShell window, type: Install-Module AzureAD
16 |
17 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
18 | #>
19 |
20 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
21 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
22 | # described in $permissionType
23 | Function AddResourcePermission($requiredAccess, `
24 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType)
25 | {
26 | foreach($permission in $requiredAccesses.Trim().Split("|"))
27 | {
28 | foreach($exposedPermission in $exposedPermissions)
29 | {
30 | if ($exposedPermission.Value -eq $permission)
31 | {
32 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
33 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
34 | $resourceAccess.Id = $exposedPermission.Id # Read directory data
35 | $requiredAccess.ResourceAccess.Add($resourceAccess)
36 | }
37 | }
38 | }
39 | }
40 |
41 | #
42 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"
43 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell
44 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal)
45 | {
46 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
47 | if ($servicePrincipal)
48 | {
49 | $sp = $servicePrincipal
50 | }
51 | else
52 | {
53 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
54 | }
55 | $appid = $sp.AppId
56 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
57 | $requiredAccess.ResourceAppId = $appid
58 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
59 |
60 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
61 | if ($requiredDelegatedPermissions)
62 | {
63 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
64 | }
65 |
66 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
67 | if ($requiredApplicationPermissions)
68 | {
69 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
70 | }
71 | return $requiredAccess
72 | }
73 |
74 |
75 | Function UpdateLine([string] $line, [string] $value)
76 | {
77 | $index = $line.IndexOf('=')
78 | $delimiter = ';'
79 | if ($index -eq -1)
80 | {
81 | $index = $line.IndexOf(':')
82 | $delimiter = ','
83 | }
84 | if ($index -ige 0)
85 | {
86 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter
87 | }
88 | return $line
89 | }
90 |
91 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
92 | {
93 | $lines = Get-Content $configFilePath
94 | $index = 0
95 | while($index -lt $lines.Length)
96 | {
97 | $line = $lines[$index]
98 | foreach($key in $dictionary.Keys)
99 | {
100 | if ($line.Contains($key))
101 | {
102 | $lines[$index] = UpdateLine $line $dictionary[$key]
103 | }
104 | }
105 | $index++
106 | }
107 |
108 | Set-Content -Path $configFilePath -Value $lines -Force
109 | }
110 |
111 | Set-Content -Value "
" -Path createdApps.html
112 | Add-Content -Value "Application | AppId | Url in the Azure portal |
" -Path createdApps.html
113 |
114 | $ErrorActionPreference = "Stop"
115 |
116 | Function ConfigureApplications
117 | {
118 | <#.Description
119 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the
120 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config)
121 | so that they are consistent with the Applications parameters
122 | #>
123 | $commonendpoint = "common"
124 |
125 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
126 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
127 |
128 | # Login to Azure PowerShell (interactive if credentials are not already provided:
129 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
130 | if (!$Credential -and $TenantId)
131 | {
132 | $creds = Connect-AzureAD -TenantId $tenantId
133 | }
134 | else
135 | {
136 | if (!$TenantId)
137 | {
138 | $creds = Connect-AzureAD -Credential $Credential
139 | }
140 | else
141 | {
142 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential
143 | }
144 | }
145 |
146 | if (!$tenantId)
147 | {
148 | $tenantId = $creds.Tenant.Id
149 | }
150 |
151 | $tenant = Get-AzureADTenantDetail
152 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name
153 |
154 | # Get the user running the script to add the user as the app owner
155 | $user = Get-AzureADUser -ObjectId $creds.Account.Id
156 |
157 | # Create the client AAD application
158 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)"
159 | # create the application
160 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" `
161 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" `
162 | -AvailableToOtherTenants $True `
163 | -PublicClient $True
164 |
165 | # create the service principal of the newly created application
166 | $currentAppId = $clientAadApplication.AppId
167 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
168 |
169 | # add the user running the script as an app owner if needed
170 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId
171 | if ($owner -eq $null)
172 | {
173 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId
174 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'"
175 | }
176 |
177 |
178 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)"
179 |
180 | # URL of the AAD application in the Azure portal
181 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
182 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
183 | Add-Content -Value "client | $currentAppId | Console-Interactive-MultiTarget-v2 |
" -Path createdApps.html
184 |
185 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
186 |
187 | # Add Required Resources Access (from 'client' to 'Microsoft Graph')
188 | Write-Host "Getting access from 'client' to 'Microsoft Graph'"
189 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
190 | -requiredDelegatedPermissions "User.Read" `
191 |
192 | $requiredResourcesAccess.Add($requiredPermissions)
193 |
194 |
195 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess
196 | Write-Host "Granted permissions."
197 |
198 | # Update config file for 'client'
199 | $configFile = $pwd.Path + "\..\Console-Interactive-MultiTarget\appsettings.json"
200 | Write-Host "Updating the sample code ($configFile)"
201 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId };
202 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary
203 |
204 | Add-Content -Value "
" -Path createdApps.html
205 | }
206 |
207 | # Pre-requisites
208 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) {
209 | Install-Module "AzureAD" -Scope CurrentUser
210 | }
211 |
212 | Import-Module AzureAD
213 |
214 | # Run interactively (will ask you for the tenant ID)
215 | ConfigureApplications -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "Sample": {
3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API from a multi-target console application.",
4 | "Level": 100,
5 | "Client": ".NET Desktop (Console)",
6 | "Service": "Microsoft Graph",
7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial",
8 | "Endpoint": "AAD v2.0"
9 | },
10 |
11 | /*
12 | This section describes the Azure AD Applications to configure, and their dependencies
13 | */
14 | "AADApps": [
15 | {
16 | "Id": "client",
17 | "Name": "Console-Interactive-MultiTarget-v2",
18 | "Kind": "Desktop",
19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost",
20 | "RequiredResourcesAccess": [
21 | {
22 | "Resource": "Microsoft Graph",
23 | "DelegatedPermissions": [ "User.Read" ]
24 | }
25 | ]
26 | }
27 | ],
28 |
29 | /*
30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps
31 | are created in Azure AD.
32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location
33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value
34 | */
35 | "CodeConfiguration": [
36 | {
37 | "App": "client",
38 | "SettingKind": "JSon",
39 | "SettingFile": "\\..\\Console-Interactive-MultiTarget\\appsettings.json",
40 | "Mappings": [
41 | {
42 | "key": "ClientId",
43 | "value": ".AppId"
44 | },
45 | {
46 | "key": "TenantId",
47 | "value": "$tenantId"
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/Console-Interactive-MultiTarget/Console-Interactive-MultiTarget.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1; net472
6 | Console_Interactive_MultiTarget
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/Console-Interactive-MultiTarget/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Graph;
3 | using Microsoft.Identity.Client;
4 | using System;
5 | using System.Linq;
6 | using System.Net.Http.Headers;
7 | using System.Threading.Tasks;
8 |
9 | namespace Console_Interactive_MultiTarget
10 | {
11 | internal class Program
12 | {
13 | private static PublicClientApplicationOptions appConfiguration = null;
14 | private static IConfiguration configuration;
15 | private static string MSGraphURL;
16 |
17 | // The MSAL Public client app
18 | private static IPublicClientApplication application;
19 |
20 | private static async Task Main(string[] args)
21 | {
22 | // Using appsettings.json for our configuration settings
23 | var builder = new ConfigurationBuilder()
24 | .SetBasePath(System.IO.Directory.GetCurrentDirectory())
25 | .AddJsonFile("appsettings.json");
26 |
27 | configuration = builder.Build();
28 |
29 | appConfiguration = configuration
30 | .Get();
31 |
32 | // We intend to obtain a token for Graph for the following scopes (permissions)
33 | string[] scopes = new[] { "user.read" };
34 |
35 | MSGraphURL = configuration.GetValue("GraphApiUrl");
36 |
37 | // Sign-in user using MSAL and obtain an access token for MS Graph
38 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes);
39 |
40 | // Call the /me endpoint of MS Graph
41 | await CallMSGraph(graphClient);
42 | }
43 |
44 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes)
45 | {
46 | string authority = string.Concat(configuration.Instance, configuration.TenantId);
47 |
48 | // Initialize the MSAL library by building a public client application
49 | application = PublicClientApplicationBuilder.Create(configuration.ClientId)
50 | .WithAuthority(authority)
51 | .WithDefaultRedirectUri()
52 | .Build();
53 |
54 |
55 | AuthenticationResult result;
56 | try
57 | {
58 | var accounts = await application.GetAccountsAsync();
59 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
60 | .ExecuteAsync();
61 | }
62 | catch (MsalUiRequiredException ex)
63 | {
64 | result = await application.AcquireTokenInteractive(scopes)
65 | .WithClaims(ex.Claims)
66 | .ExecuteAsync();
67 | }
68 |
69 | return result.AccessToken;
70 | }
71 |
72 | ///
73 | /// Sign in user using MSAL and obtain a token for MS Graph
74 | ///
75 | ///
76 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes)
77 | {
78 | GraphServiceClient graphClient = new GraphServiceClient(MSGraphURL,
79 | new DelegateAuthenticationProvider(async (requestMessage) =>
80 | {
81 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes));
82 | }));
83 |
84 | return await Task.FromResult(graphClient);
85 | }
86 |
87 | ///
88 | /// Call MS Graph and print results
89 | ///
90 | ///
91 | ///
92 | private static async Task CallMSGraph(GraphServiceClient graphClient)
93 | {
94 | var me = await graphClient.Me.Request().GetAsync();
95 |
96 | // Printing the results
97 | Console.WriteLine("-------- Data from call to MS Graph --------");
98 | Console.Write(Environment.NewLine);
99 | Console.WriteLine($"Id: {me.Id}");
100 | Console.WriteLine($"Display Name: {me.DisplayName}");
101 | Console.WriteLine($"Email: {me.Mail}");
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/Console-Interactive-MultiTarget/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Instance": "https://login.microsoftonline.com/",
3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
4 | "TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
5 | "RedirectUri": "http://localhost",
6 | "GraphApiUrl": "https://graph.microsoft.com/v1.0/"
7 | }
8 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/Console-Interactive.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29709.97
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console-Interactive-MultiTarget", "Console-Interactive-MultiTarget\Console-Interactive-MultiTarget.csproj", "{D8504453-21E5-4381-9485-454EBC88C734}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {D8504453-21E5-4381-9485-454EBC88C734}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {D8504453-21E5-4381-9485-454EBC88C734}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {D8504453-21E5-4381-9485-454EBC88C734}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {D8504453-21E5-4381-9485-454EBC88C734}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {42EE218E-9522-4DBA-9DFE-2EC0FB0CD3D5}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-1-AzureAD/ReadmeFiles/topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/1-Calling-MSGraph/1-1-AzureAD/ReadmeFiles/topology.png
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-2-AzureB2C/Placeholder.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/1-Calling-MSGraph/1-2-AzureB2C/Placeholder.md
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-3-NationalClouds/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | services: active-directory
3 | platforms: dotnet
4 | author: TiagoBrenck
5 | level: 100
6 | client: .NET Desktop (Console)
7 | service: Microsoft Graph
8 | endpoint: Microsoft identity platform
9 | page_type: sample
10 | languages:
11 | - csharp
12 | products:
13 | - azure
14 | - microsoft-entra-id
15 | - dotnet
16 | - office-ms-graph
17 | description: "This sample demonstrates a .NET Desktop (Console) application calling Microsoft Graph on National clouds"
18 | ---
19 |
20 | # Using the Microsoft identity platform to call Microsoft Graph API from a multi-target console application on National clouds.
21 |
22 | ## About this sample
23 |
24 | ### Overview
25 |
26 | This sample demonstrates a .NET Desktop (Console) application calling Microsoft Graph on National clouds.
27 |
28 | 1. The .NET Desktop (Console) application uses the Microsoft Authentication Library (MSAL) to obtain a JWT access token from a National cloud Microsoft Entra ID
29 | 2. The access token is used as a bearer token to authenticate the user when calling Microsoft Graph.
30 |
31 | 
32 |
33 | National clouds (aka Sovereign clouds) are physically isolated instances of Azure. These regions of Azure are designed to make sure that data residency, sovereignty, and compliance requirements are honored within geographical boundaries.
34 |
35 | In addition to the public cloud, Azure Active Directory is deployed in the following National clouds:
36 |
37 | - Azure US Government
38 | - Azure China 21Vianet
39 | - Azure Germany
40 |
41 | Note that enabling your application for National clouds requires you to:
42 |
43 | - register your application in a specific portal, depending on the cloud
44 | - use a specific authority, depending on the cloud in the config file for your application
45 | - in case you want to call the graph, this requires a specific Graph endpoint URL, depending on the cloud.
46 |
47 | More details in [Authentication in National Clouds](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud)
48 |
49 | ## How to run this sample
50 |
51 | To run this sample, you can follow the same steps on [the first sample in this tutorial](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/1-Calling-MSGraph/1-1-AzureAD) but using the desired National cloud portal to create the application.
52 |
53 | To use the PowerShell script that **automatically** creates the Microsoft Entra application for this sample in your National cloud, please use the parameter `-AzureEnvironmentName` [as described in this document](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/blob/master/1-Calling-MSGraph/1-1-AzureAD/AppCreationScripts/AppCreationScripts.md#running-the-script-on-azure-sovereign-clouds).
54 |
55 | ### Configure the client app (Console-Interactive-MultiTarget-v2) to use your app registration
56 |
57 | Open the project in your IDE (like Visual Studio) to configure the code.
58 |
59 | 1. Open the `Console-Interactive-MultiTarget\appsettings.json` file
60 | 1. Find the app key `Instance` and replace the existing value with the [correspondent endpoint for your National clouds](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud#azure-ad-authentication-endpoints).
61 | 1. Find the app key `ClientId` and replace the existing value with the application ID (clientId) of the `Console-Interactive-MultiTarget-v2` application copied from the Microsoft Entra admin center.
62 | 1. Find the app key `TenantId` and replace the existing value with your Microsoft Entra tenant ID.
63 | 1. Find the app key `GraphApiUrl` and replace the existing value with the Microsoft Graph endpoint for your National clouds. [See this reference for more info on which graph endpoint to use](https://docs.microsoft.com/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints).
64 |
65 | ## Community Help and Support
66 |
67 | Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community.
68 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
69 | Make sure that your questions or comments are tagged with [`azure-active-directory` `msal` `dotnet`].
70 |
71 | If you find a bug in the sample, please raise the issue on [GitHub Issues](../../../../issues).
72 |
73 | To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory).
74 |
75 | > [Consider taking a moment to share your experience with us.](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR73pcsbpbxNJuZCMKN0lURpUREhEVDBOTFBMUVRPUElBUE5WMjdPQ1RaMiQlQCN0PWcu)
76 |
77 | ## Contributing
78 |
79 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md).
80 |
81 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
82 |
83 | ## More information
84 |
85 | For more information, see MSAL.NET's conceptual documentation:
86 |
87 | - [MSAL.NET's conceptual documentation](https://aka.ms/msal-net)
88 | - [Microsoft identity platform (Azure Active Directory for developers)](https://docs.microsoft.com/azure/active-directory/develop/)
89 | - [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app)
90 | - [Quickstart: Configure a client application to access web APIs](https://docs.microsoft.com/azure/active-directory/develop/quickstart-configure-app-access-web-apis)
91 |
92 | - [Understanding Microsoft Entra application consent experiences](https://docs.microsoft.com/azure/active-directory/develop/application-consent-experience)
93 | - [Understand user and admin consent](https://docs.microsoft.com/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant#understand-user-and-admin-consent)
94 | - [Application and service principal objects in Microsoft Entra ID](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals)
95 |
96 | For more information about how OAuth 2.0 protocols work in this scenario and other scenarios, see [Authentication Scenarios for Microsoft Entra ID](http://go.microsoft.com/fwlink/?LinkId=394414).
97 |
--------------------------------------------------------------------------------
/1-Calling-MSGraph/1-3-NationalClouds/ReadmeFiles/topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/1-Calling-MSGraph/1-3-NationalClouds/ReadmeFiles/topology.png
--------------------------------------------------------------------------------
/2-TokenCache/AppCreationScripts/AppCreationScripts.md:
--------------------------------------------------------------------------------
1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts
2 |
3 | ## Overview
4 |
5 | ### Quick summary
6 |
7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory
8 | 1. In PowerShell run:
9 | ```PowerShell
10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
11 | ```
12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below)
13 | ```PowerShell
14 | cd .\AppCreationScripts\
15 | .\Configure.ps1
16 | ```
17 | 1. Open the Visual Studio solution and click start
18 |
19 | ### More details
20 |
21 | The following paragraphs:
22 |
23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios.
24 | - Explain the [pre-requisites](#pre-requisites)
25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script):
26 | - [Interactively](#option-1-interactive) to create the app in your home tenant
27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant
28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant)
29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant)
30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds)
31 |
32 | ## Goal of the scripts
33 |
34 | ### Presentation of the scripts
35 |
36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra applications, and the configuration of the code for this sample. Once you run them, you will only need to build the solution and you are good to test.
37 |
38 | These scripts are:
39 |
40 | - `Configure.ps1` which:
41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets),
42 | - changes the configuration files in the C# and JavaScript projects.
43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created:
44 | - the identifier of the application
45 | - the AppId of the application
46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com).
47 |
48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset).
49 |
50 | ### Usage pattern for tests and DevOps scenarios
51 |
52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below.
53 |
54 | ## How to use the app creation scripts?
55 |
56 | ### Pre-requisites
57 |
58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window)
59 | 2. Navigate to the root directory of the project.
60 | 3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command:
61 | ```PowerShell
62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
63 | ```
64 | ### (Optionally) install AzureAD PowerShell modules
65 | The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps:
66 |
67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
68 |
69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator).
70 | 2. Type:
71 | ```PowerShell
72 | Install-Module AzureAD
73 | ```
74 |
75 | or if you cannot be administrator on your machine, run:
76 | ```PowerShell
77 | Install-Module AzureAD -Scope CurrentUser
78 | ```
79 |
80 | ### Run the script and start running
81 |
82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo,
83 | ```PowerShell
84 | cd AppCreationScripts
85 | ```
86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that.
87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**.
88 | 8. select **Start** for the projects
89 |
90 | You're done. this just works!
91 |
92 | ### Four ways to run the script
93 |
94 | We advise four ways of running the script:
95 |
96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects,
97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects,
98 | - Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects,
99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects.
100 |
101 | Here are the details on how to do this.
102 |
103 | #### Option 1 (interactive)
104 |
105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA).
106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined.
107 |
108 | Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in.
109 |
110 | #### Option 2 (non-interactive)
111 |
112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window
113 |
114 | ```PowerShell
115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
117 | . .\Cleanup.ps1 -Credential $mycreds
118 | . .\Configure.ps1 -Credential $mycreds
119 | ```
120 |
121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault.
122 |
123 | #### Option 3 (Interactive, but create apps in a specified tenant)
124 |
125 | if you want to create the apps in a particular tenant, you can use the following option:
126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com)
127 | - Select the Microsoft Entra tenant you are interested in (in the combo-box below your name on the top right of the browser window)
128 | - Find the "Active Directory" object in this tenant
129 | - Go to **Properties** and copy the content of the **Directory Id** property
130 | - Then use the full syntax to run the scripts:
131 |
132 | ```PowerShell
133 | $tenantId = "yourTenantIdGuid"
134 | . .\Cleanup.ps1 -TenantId $tenantId
135 | . .\Configure.ps1 -TenantId $tenantId
136 | ```
137 |
138 | #### Option 4 (non-interactive, and create apps in a specified tenant)
139 |
140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run:
141 |
142 | ```PowerShell
143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
145 | $tenantId = "yourTenantIdGuid"
146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId
147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId
148 | ```
149 |
150 | ### Running the script on Azure Sovereign clouds
151 |
152 | All the four options listed above, can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`.
153 |
154 | The acceptable values for this parameter are:
155 |
156 | - AzureCloud
157 | - AzureChinaCloud
158 | - AzureUSGovernment
159 | - AzureGermanyCloud
160 |
161 | Example:
162 |
163 | ```PowerShell
164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud"
165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud"
166 | ```
167 |
--------------------------------------------------------------------------------
/2-TokenCache/AppCreationScripts/Cleanup.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) {
11 | Install-Module "AzureAD" -Scope CurrentUser
12 | }
13 | Import-Module AzureAD
14 | $ErrorActionPreference = "Stop"
15 |
16 | Function Cleanup
17 | {
18 | if (!$azureEnvironmentName)
19 | {
20 | $azureEnvironmentName = "AzureCloud"
21 | }
22 |
23 | <#
24 | .Description
25 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script
26 | #>
27 |
28 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
29 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
30 |
31 | # Login to Azure PowerShell (interactive if credentials are not already provided:
32 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
33 | if (!$Credential -and $TenantId)
34 | {
35 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
36 | }
37 | else
38 | {
39 | if (!$TenantId)
40 | {
41 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
42 | }
43 | else
44 | {
45 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
46 | }
47 | }
48 |
49 | if (!$tenantId)
50 | {
51 | $tenantId = $creds.Tenant.Id
52 | }
53 | $tenant = Get-AzureADTenantDetail
54 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name
55 |
56 | # Removes the applications
57 | Write-Host "Cleaning-up applications from tenant '$tenantName'"
58 |
59 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed"
60 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId }
61 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'"
62 | if ($apps)
63 | {
64 | Remove-AzureADApplication -ObjectId $apps.ObjectId
65 | }
66 |
67 | foreach ($app in $apps)
68 | {
69 | Remove-AzureADApplication -ObjectId $app.ObjectId
70 | Write-Host "Removed Console-Interactive-MultiTarget-v2.."
71 | }
72 | # also remove service principals of this app
73 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false}
74 |
75 | }
76 |
77 | Cleanup -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/2-TokenCache/AppCreationScripts/Configure.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | <#
11 | This script creates the Azure AD applications needed for this sample and updates the configuration files
12 | for the visual Studio projects from the data in the Azure AD applications.
13 |
14 | Before running this script you need to install the AzureAD cmdlets as an administrator.
15 | For this:
16 | 1) Run Powershell as an administrator
17 | 2) in the PowerShell window, type: Install-Module AzureAD
18 |
19 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
20 | #>
21 |
22 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
23 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
24 | # described in $permissionType
25 | Function AddResourcePermission($requiredAccess, `
26 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType)
27 | {
28 | foreach($permission in $requiredAccesses.Trim().Split("|"))
29 | {
30 | foreach($exposedPermission in $exposedPermissions)
31 | {
32 | if ($exposedPermission.Value -eq $permission)
33 | {
34 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
35 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
36 | $resourceAccess.Id = $exposedPermission.Id # Read directory data
37 | $requiredAccess.ResourceAccess.Add($resourceAccess)
38 | }
39 | }
40 | }
41 | }
42 |
43 | #
44 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"
45 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell
46 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal)
47 | {
48 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
49 | if ($servicePrincipal)
50 | {
51 | $sp = $servicePrincipal
52 | }
53 | else
54 | {
55 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
56 | }
57 | $appid = $sp.AppId
58 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
59 | $requiredAccess.ResourceAppId = $appid
60 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
61 |
62 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
63 | if ($requiredDelegatedPermissions)
64 | {
65 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
66 | }
67 |
68 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
69 | if ($requiredApplicationPermissions)
70 | {
71 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
72 | }
73 | return $requiredAccess
74 | }
75 |
76 |
77 | Function UpdateLine([string] $line, [string] $value)
78 | {
79 | $index = $line.IndexOf('=')
80 | $delimiter = ';'
81 | if ($index -eq -1)
82 | {
83 | $index = $line.IndexOf(':')
84 | $delimiter = ','
85 | }
86 | if ($index -ige 0)
87 | {
88 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter
89 | }
90 | return $line
91 | }
92 |
93 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
94 | {
95 | $lines = Get-Content $configFilePath
96 | $index = 0
97 | while($index -lt $lines.Length)
98 | {
99 | $line = $lines[$index]
100 | foreach($key in $dictionary.Keys)
101 | {
102 | if ($line.Contains($key))
103 | {
104 | $lines[$index] = UpdateLine $line $dictionary[$key]
105 | }
106 | }
107 | $index++
108 | }
109 |
110 | Set-Content -Path $configFilePath -Value $lines -Force
111 | }
112 |
113 | Set-Content -Value "" -Path createdApps.html
114 | Add-Content -Value "Application | AppId | Url in the Azure portal |
" -Path createdApps.html
115 |
116 | $ErrorActionPreference = "Stop"
117 |
118 | Function ConfigureApplications
119 | {
120 | <#.Description
121 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the
122 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config)
123 | so that they are consistent with the Applications parameters
124 | #>
125 | $commonendpoint = "common"
126 |
127 | if (!$azureEnvironmentName)
128 | {
129 | $azureEnvironmentName = "AzureCloud"
130 | }
131 |
132 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
133 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
134 |
135 | # Login to Azure PowerShell (interactive if credentials are not already provided:
136 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
137 | if (!$Credential -and $TenantId)
138 | {
139 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
140 | }
141 | else
142 | {
143 | if (!$TenantId)
144 | {
145 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
146 | }
147 | else
148 | {
149 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
150 | }
151 | }
152 |
153 | if (!$tenantId)
154 | {
155 | $tenantId = $creds.Tenant.Id
156 | }
157 |
158 |
159 |
160 | $tenant = Get-AzureADTenantDetail
161 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name
162 |
163 | # Get the user running the script to add the user as the app owner
164 | $user = Get-AzureADUser -ObjectId $creds.Account.Id
165 |
166 | # Create the client AAD application
167 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)"
168 | # create the application
169 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" `
170 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" `
171 | -AvailableToOtherTenants $True `
172 | -PublicClient $True
173 |
174 | # create the service principal of the newly created application
175 | $currentAppId = $clientAadApplication.AppId
176 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
177 |
178 | # add the user running the script as an app owner if needed
179 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId
180 | if ($owner -eq $null)
181 | {
182 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId
183 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'"
184 | }
185 |
186 |
187 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)"
188 |
189 | # URL of the AAD application in the Azure portal
190 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
191 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
192 | Add-Content -Value "client | $currentAppId | Console-Interactive-MultiTarget-v2 |
" -Path createdApps.html
193 |
194 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
195 |
196 | # Add Required Resources Access (from 'client' to 'Microsoft Graph')
197 | Write-Host "Getting access from 'client' to 'Microsoft Graph'"
198 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
199 | -requiredDelegatedPermissions "User.Read" `
200 |
201 | $requiredResourcesAccess.Add($requiredPermissions)
202 |
203 |
204 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess
205 | Write-Host "Granted permissions."
206 |
207 | # Update config file for 'client'
208 | $configFile = $pwd.Path + "\..\Console-TokenCache\appsettings.json"
209 | Write-Host "Updating the sample code ($configFile)"
210 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId };
211 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary
212 |
213 | Add-Content -Value "
" -Path createdApps.html
214 | }
215 |
216 | # Pre-requisites
217 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) {
218 | Install-Module "AzureAD" -Scope CurrentUser
219 | }
220 |
221 | Import-Module AzureAD
222 |
223 | # Run interactively (will ask you for the tenant ID)
224 | ConfigureApplications -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/2-TokenCache/AppCreationScripts/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "Sample": {
3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API from a multi-target console application, with token cache",
4 | "Level": 200,
5 | "Client": ".NET Desktop (Console)",
6 | "Service": "Microsoft Graph",
7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial",
8 | "Endpoint": "AAD v2.0"
9 | },
10 |
11 | /*
12 | This section describes the Azure AD Applications to configure, and their dependencies
13 | */
14 | "AADApps": [
15 | {
16 | "Id": "client",
17 | "Name": "Console-Interactive-MultiTarget-v2",
18 | "Kind": "Desktop",
19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost",
20 | "RequiredResourcesAccess": [
21 | {
22 | "Resource": "Microsoft Graph",
23 | "DelegatedPermissions": [ "User.Read" ]
24 | }
25 | ]
26 | }
27 | ],
28 |
29 | /*
30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps
31 | are created in Azure AD.
32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location
33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value
34 | */
35 | "CodeConfiguration": [
36 | {
37 | "App": "client",
38 | "SettingKind": "JSon",
39 | "SettingFile": "\\..\\Console-TokenCache\\appsettings.json",
40 | "Mappings": [
41 | {
42 | "key": "ClientId",
43 | "value": ".AppId"
44 | },
45 | {
46 | "key": "TenantId",
47 | "value": "$tenantId"
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/2-TokenCache/Console-TokenCache.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29424.173
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console-TokenCache", "Console-TokenCache\Console-TokenCache.csproj", "{9286F81F-F57A-4B43-99AD-A7789771EC4C}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {9286F81F-F57A-4B43-99AD-A7789771EC4C}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {305EF8E8-BB35-44FD-A26D-546A38F197EC}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/2-TokenCache/Console-TokenCache/CacheSettings.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Identity.Client.Extensions.Msal;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace Console_TokenCache
6 | {
7 | public static class CacheSettings
8 | {
9 | // computing the root directory is not very simple on Linux and Mac, so a helper is provided
10 | private static readonly string s_cacheFilePath =
11 | Path.Combine(MsalCacheHelper.UserRootDirectory, "msal.contoso.cache");
12 |
13 | public static readonly string CacheFileName = Path.GetFileName(s_cacheFilePath);
14 | public static readonly string CacheDir = Path.GetDirectoryName(s_cacheFilePath);
15 |
16 |
17 | public static readonly string KeyChainServiceName = "Contoso.MyProduct";
18 | public static readonly string KeyChainAccountName = "MSALCache";
19 |
20 | public static readonly string LinuxKeyRingSchema = "com.contoso.devtools.tokencache";
21 | public static readonly string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection;
22 | public static readonly string LinuxKeyRingLabel = "MSAL token cache for all Contoso dev tool apps.";
23 | public static readonly KeyValuePair LinuxKeyRingAttr1 = new KeyValuePair("Version", "1");
24 | public static readonly KeyValuePair LinuxKeyRingAttr2 = new KeyValuePair("ProductGroup", "MyApps");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/2-TokenCache/Console-TokenCache/Console-TokenCache.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1; net472
6 | Console_TokenCache
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/2-TokenCache/Console-TokenCache/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Graph;
3 | using Microsoft.Identity.Client;
4 | using Microsoft.Identity.Client.Extensions.Msal;
5 | using System;
6 | using System.Linq;
7 | using System.Net.Http.Headers;
8 | using System.Threading.Tasks;
9 |
10 | namespace Console_TokenCache
11 | {
12 | class Program
13 | {
14 | private static PublicClientApplicationOptions appConfiguration = null;
15 | private static IConfiguration configuration;
16 | private static string _authority;
17 |
18 | static async Task Main(string[] _)
19 | {
20 | // Using appsettings.json as our configuration settings
21 | var builder = new ConfigurationBuilder()
22 | .SetBasePath(System.IO.Directory.GetCurrentDirectory())
23 | .AddJsonFile("appsettings.json");
24 |
25 | configuration = builder.Build();
26 |
27 | // Loading PublicClientApplicationOptions from the values set on appsettings.json
28 | appConfiguration = configuration
29 | .Get();
30 |
31 | // Building the AAD authority, https://login.microsoftonline.com/
32 | _authority = string.Concat(appConfiguration.Instance, appConfiguration.TenantId);
33 |
34 | // Building a public client application
35 | var app = PublicClientApplicationBuilder.Create(appConfiguration.ClientId)
36 | .WithAuthority(_authority)
37 | .WithRedirectUri(appConfiguration.RedirectUri)
38 | .Build();
39 |
40 | // Building StorageCreationProperties
41 | var storageProperties =
42 | new StorageCreationPropertiesBuilder(CacheSettings.CacheFileName, CacheSettings.CacheDir, appConfiguration.ClientId)
43 | .WithLinuxKeyring(
44 | CacheSettings.LinuxKeyRingSchema,
45 | CacheSettings.LinuxKeyRingCollection,
46 | CacheSettings.LinuxKeyRingLabel,
47 | CacheSettings.LinuxKeyRingAttr1,
48 | CacheSettings.LinuxKeyRingAttr2)
49 | .WithMacKeyChain(
50 | CacheSettings.KeyChainServiceName,
51 | CacheSettings.KeyChainAccountName)
52 | .Build();
53 |
54 | // This hooks up the cross-platform cache into MSAL
55 | var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
56 | cacheHelper.RegisterCache(app.UserTokenCache);
57 |
58 | // Scope for Microsoft Graph
59 | string[] scopes = new[] { "user.read" };
60 | string graphApiUrl = configuration.GetValue("GraphApiUrl");
61 |
62 | AuthenticationResult result;
63 | GraphServiceClient graphClient;
64 | User me;
65 |
66 | while (true)
67 | {
68 | // Display menu
69 | Console.WriteLine("------------ MENU ------------");
70 | Console.WriteLine("1. Acquire Token Silent / Interactive (not using embedded view)");
71 | Console.WriteLine("2. Acquire Token Silent / Interactive (using embedded view, currently not supported on .NET Core)");
72 | Console.WriteLine("3. Display Accounts (reads the cache)");
73 | Console.WriteLine("4. Clear cache");
74 | Console.WriteLine("x. Exit app");
75 | Console.Write("Enter your Selection:");
76 | char.TryParse(Console.ReadLine(), out var selection);
77 |
78 | try
79 | {
80 | switch (selection)
81 | {
82 | case '1': // Silent / Interactive
83 | Console.Clear();
84 | Console.WriteLine("Acquiring token from the cache (silently), if it fails do it interactively");
85 |
86 | result = await AcquireToken(app, scopes, false);
87 |
88 | graphClient = GetGraphServiceClient(result.AccessToken, graphApiUrl);
89 | me = await graphClient.Me.Request().GetAsync();
90 |
91 | DisplayGraphResult(result, me);
92 | break;
93 |
94 | case '2': // Silent / Interactive with embedded view
95 | Console.Clear();
96 | Console.WriteLine("Acquiring token from the cache (silently), if it fails do it interactively using embedded view");
97 |
98 | result = await AcquireToken(app, scopes, true);
99 |
100 | graphClient = GetGraphServiceClient(result.AccessToken, graphApiUrl);
101 | me = await graphClient.Me.Request().GetAsync();
102 |
103 | DisplayGraphResult(result, me);
104 | break;
105 |
106 | case '3': // Display Accounts
107 | Console.Clear();
108 | var accounts2 = await app.GetAccountsAsync().ConfigureAwait(false);
109 | if (!accounts2.Any())
110 | {
111 | Console.WriteLine("No accounts were found in the cache.");
112 | Console.Write(Environment.NewLine);
113 | }
114 |
115 | foreach (var acc in accounts2)
116 | {
117 | Console.WriteLine($"Account for {acc.Username}");
118 | Console.Write(Environment.NewLine);
119 | }
120 | break;
121 |
122 | case '4': // Clear cache
123 | Console.Clear();
124 | var accounts3 = await app.GetAccountsAsync().ConfigureAwait(false);
125 | foreach (var acc in accounts3)
126 | {
127 | Console.WriteLine($"Removing account for {acc.Username}");
128 | Console.Write(Environment.NewLine);
129 | await app.RemoveAsync(acc).ConfigureAwait(false);
130 | }
131 | break;
132 |
133 | case 'x':
134 | return;
135 | }
136 |
137 | }
138 | catch (Exception ex)
139 | {
140 | Console.ForegroundColor = ConsoleColor.Red;
141 | Console.WriteLine("Exception : " + ex);
142 | Console.ResetColor();
143 | Console.WriteLine("Hit Enter to continue");
144 | Console.Read();
145 | Console.Clear();
146 | }
147 | }
148 | }
149 |
150 | private static async Task AcquireToken(IPublicClientApplication app, string[] scopes, bool useEmbaddedView)
151 | {
152 | AuthenticationResult result;
153 | try
154 | {
155 | var accounts = await app.GetAccountsAsync();
156 |
157 | // Try to acquire an access token from the cache. If an interaction is required,
158 | // MsalUiRequiredException will be thrown.
159 | result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
160 | .ExecuteAsync();
161 | }
162 | catch (MsalUiRequiredException)
163 | {
164 | // Acquiring an access token interactively. MSAL will cache it so we can use AcquireTokenSilent
165 | // on future calls.
166 | result = await app.AcquireTokenInteractive(scopes)
167 | .WithUseEmbeddedWebView(useEmbaddedView)
168 | .ExecuteAsync();
169 | }
170 |
171 | return result;
172 | }
173 |
174 | private static GraphServiceClient GetGraphServiceClient(string accessToken, string graphApiUrl)
175 | {
176 | GraphServiceClient graphServiceClient = new GraphServiceClient(graphApiUrl,
177 | new DelegateAuthenticationProvider(
178 | async (requestMessage) =>
179 | {
180 | await Task.Run(() =>
181 | {
182 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
183 | });
184 | }));
185 |
186 | return graphServiceClient;
187 | }
188 |
189 | private static void DisplayGraphResult(AuthenticationResult result, User me)
190 | {
191 | Console.ForegroundColor = ConsoleColor.Green;
192 |
193 | Console.Write(Environment.NewLine);
194 | Console.WriteLine($"Hello {result.Account.Username}");
195 | Console.Write(Environment.NewLine);
196 | Console.WriteLine("-------- GRAPH RESULT --------");
197 | Console.Write(Environment.NewLine);
198 | Console.WriteLine($"Id: {me.Id}");
199 | Console.WriteLine($"Display Name: {me.DisplayName}");
200 | Console.WriteLine($"Email: {me.Mail}");
201 | Console.Write(Environment.NewLine);
202 | Console.WriteLine("------------------------------");
203 | Console.Write(Environment.NewLine);
204 | Console.Write(Environment.NewLine);
205 | Console.ResetColor();
206 |
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/2-TokenCache/Console-TokenCache/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Instance": "https://login.microsoftonline.com/",
3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
4 | "TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
5 | "RedirectUri": "http://localhost",
6 | "GraphApiUrl": "https://graph.microsoft.com/beta/"
7 | }
8 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/AppCreationScripts.md:
--------------------------------------------------------------------------------
1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts
2 |
3 | ## Overview
4 |
5 | ### Quick summary
6 |
7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory
8 | 1. In PowerShell run:
9 | ```PowerShell
10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
11 | ```
12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below)
13 | ```PowerShell
14 | cd .\AppCreationScripts\
15 | .\Configure.ps1
16 | ```
17 | 1. Open the Visual Studio solution and click start
18 |
19 | ### More details
20 |
21 | The following paragraphs:
22 |
23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios.
24 | - Explain the [pre-requisites](#pre-requisites)
25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script):
26 | - [Interactively](#option-1-interactive) to create the app in your home tenant
27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant
28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant)
29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant)
30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds)
31 |
32 | ## Goal of the scripts
33 |
34 | ### Presentation of the scripts
35 |
36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra applications, and the configuration of the code for this sample. Once you run them, you will only need to build the solution and you are good to test.
37 |
38 | These scripts are:
39 |
40 | - `Configure.ps1` which:
41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets),
42 | - changes the configuration files in the C# and JavaScript projects.
43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created:
44 | - the identifier of the application
45 | - the AppId of the application
46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com).
47 |
48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset).
49 |
50 | ### Usage pattern for tests and DevOps scenarios
51 |
52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below.
53 |
54 | ## How to use the app creation scripts?
55 |
56 | ### Pre-requisites
57 |
58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window)
59 | 2. Navigate to the root directory of the project.
60 | 3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command:
61 | ```PowerShell
62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
63 | ```
64 | ### (Optionally) install AzureAD PowerShell modules
65 | The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps:
66 |
67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
68 |
69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator).
70 | 2. Type:
71 | ```PowerShell
72 | Install-Module AzureAD
73 | ```
74 |
75 | or if you cannot be administrator on your machine, run:
76 | ```PowerShell
77 | Install-Module AzureAD -Scope CurrentUser
78 | ```
79 |
80 | ### Run the script and start running
81 |
82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo,
83 | ```PowerShell
84 | cd AppCreationScripts
85 | ```
86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that.
87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**.
88 | 8. select **Start** for the projects
89 |
90 | You're done. this just works!
91 |
92 | ### Four ways to run the script
93 |
94 | We advise four ways of running the script:
95 |
96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects,
97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects,
98 | - Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects,
99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects.
100 |
101 | Here are the details on how to do this.
102 |
103 | #### Option 1 (interactive)
104 |
105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA).
106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined.
107 |
108 | Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in.
109 |
110 | #### Option 2 (non-interactive)
111 |
112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window
113 |
114 | ```PowerShell
115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
117 | . .\Cleanup.ps1 -Credential $mycreds
118 | . .\Configure.ps1 -Credential $mycreds
119 | ```
120 |
121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault.
122 |
123 | #### Option 3 (Interactive, but create apps in a specified tenant)
124 |
125 | if you want to create the apps in a particular tenant, you can use the following option:
126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com)
127 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window)
128 | - Find the "Active Directory" object in this tenant
129 | - Go to **Properties** and copy the content of the **Directory Id** property
130 | - Then use the full syntax to run the scripts:
131 |
132 | ```PowerShell
133 | $tenantId = "yourTenantIdGuid"
134 | . .\Cleanup.ps1 -TenantId $tenantId
135 | . .\Configure.ps1 -TenantId $tenantId
136 | ```
137 |
138 | #### Option 4 (non-interactive, and create apps in a specified tenant)
139 |
140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run:
141 |
142 | ```PowerShell
143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
145 | $tenantId = "yourTenantIdGuid"
146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId
147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId
148 | ```
149 |
150 | ### Running the script on Azure Sovereign clouds
151 |
152 | All the four options listed above, can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`.
153 |
154 | The acceptable values for this parameter are:
155 |
156 | - AzureCloud
157 | - AzureChinaCloud
158 | - AzureUSGovernment
159 | - AzureGermanyCloud
160 |
161 | Example:
162 |
163 | ```PowerShell
164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud"
165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud"
166 | ```
167 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/Cleanup.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) {
11 | Install-Module "AzureAD" -Scope CurrentUser
12 | }
13 | Import-Module AzureAD
14 | $ErrorActionPreference = "Stop"
15 |
16 | Function Cleanup
17 | {
18 | if (!$azureEnvironmentName)
19 | {
20 | $azureEnvironmentName = "AzureCloud"
21 | }
22 |
23 | <#
24 | .Description
25 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script
26 | #>
27 |
28 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
29 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
30 |
31 | # Login to Azure PowerShell (interactive if credentials are not already provided:
32 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
33 | if (!$Credential -and $TenantId)
34 | {
35 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
36 | }
37 | else
38 | {
39 | if (!$TenantId)
40 | {
41 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
42 | }
43 | else
44 | {
45 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
46 | }
47 | }
48 |
49 | if (!$tenantId)
50 | {
51 | $tenantId = $creds.Tenant.Id
52 | }
53 | $tenant = Get-AzureADTenantDetail
54 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name
55 |
56 | # Removes the applications
57 | Write-Host "Cleaning-up applications from tenant '$tenantName'"
58 |
59 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed"
60 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId }
61 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'"
62 | if ($apps)
63 | {
64 | Remove-AzureADApplication -ObjectId $apps.ObjectId
65 | }
66 |
67 | foreach ($app in $apps)
68 | {
69 | Remove-AzureADApplication -ObjectId $app.ObjectId
70 | Write-Host "Removed Console-Interactive-MultiTarget-v2.."
71 | }
72 | # also remove service principals of this app
73 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false}
74 |
75 | }
76 |
77 | Cleanup -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/Configure.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | <#
11 | This script creates the Azure AD applications needed for this sample and updates the configuration files
12 | for the visual Studio projects from the data in the Azure AD applications.
13 |
14 | Before running this script you need to install the AzureAD cmdlets as an administrator.
15 | For this:
16 | 1) Run Powershell as an administrator
17 | 2) in the PowerShell window, type: Install-Module AzureAD
18 |
19 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
20 | #>
21 |
22 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
23 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
24 | # described in $permissionType
25 | Function AddResourcePermission($requiredAccess, `
26 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType)
27 | {
28 | foreach($permission in $requiredAccesses.Trim().Split("|"))
29 | {
30 | foreach($exposedPermission in $exposedPermissions)
31 | {
32 | if ($exposedPermission.Value -eq $permission)
33 | {
34 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
35 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
36 | $resourceAccess.Id = $exposedPermission.Id # Read directory data
37 | $requiredAccess.ResourceAccess.Add($resourceAccess)
38 | }
39 | }
40 | }
41 | }
42 |
43 | #
44 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"
45 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell
46 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal)
47 | {
48 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
49 | if ($servicePrincipal)
50 | {
51 | $sp = $servicePrincipal
52 | }
53 | else
54 | {
55 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
56 | }
57 | $appid = $sp.AppId
58 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
59 | $requiredAccess.ResourceAppId = $appid
60 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
61 |
62 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
63 | if ($requiredDelegatedPermissions)
64 | {
65 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
66 | }
67 |
68 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
69 | if ($requiredApplicationPermissions)
70 | {
71 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
72 | }
73 | return $requiredAccess
74 | }
75 |
76 |
77 | Function UpdateLine([string] $line, [string] $value)
78 | {
79 | $index = $line.IndexOf('=')
80 | $delimiter = ';'
81 | if ($index -eq -1)
82 | {
83 | $index = $line.IndexOf(':')
84 | $delimiter = ','
85 | }
86 | if ($index -ige 0)
87 | {
88 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter
89 | }
90 | return $line
91 | }
92 |
93 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
94 | {
95 | $lines = Get-Content $configFilePath
96 | $index = 0
97 | while($index -lt $lines.Length)
98 | {
99 | $line = $lines[$index]
100 | foreach($key in $dictionary.Keys)
101 | {
102 | if ($line.Contains($key))
103 | {
104 | $lines[$index] = UpdateLine $line $dictionary[$key]
105 | }
106 | }
107 | $index++
108 | }
109 |
110 | Set-Content -Path $configFilePath -Value $lines -Force
111 | }
112 |
113 | Set-Content -Value "" -Path createdApps.html
114 | Add-Content -Value "Application | AppId | Url in the Azure portal |
" -Path createdApps.html
115 |
116 | $ErrorActionPreference = "Stop"
117 |
118 | Function ConfigureApplications
119 | {
120 | <#.Description
121 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the
122 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config)
123 | so that they are consistent with the Applications parameters
124 | #>
125 | $commonendpoint = "common"
126 |
127 | if (!$azureEnvironmentName)
128 | {
129 | $azureEnvironmentName = "AzureCloud"
130 | }
131 |
132 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
133 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
134 |
135 | # Login to Azure PowerShell (interactive if credentials are not already provided:
136 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
137 | if (!$Credential -and $TenantId)
138 | {
139 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
140 | }
141 | else
142 | {
143 | if (!$TenantId)
144 | {
145 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
146 | }
147 | else
148 | {
149 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
150 | }
151 | }
152 |
153 | if (!$tenantId)
154 | {
155 | $tenantId = $creds.Tenant.Id
156 | }
157 |
158 |
159 |
160 | $tenant = Get-AzureADTenantDetail
161 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name
162 |
163 | # Get the user running the script to add the user as the app owner
164 | $user = Get-AzureADUser -ObjectId $creds.Account.Id
165 |
166 | # Create the client AAD application
167 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)"
168 | # create the application
169 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" `
170 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" `
171 | -AvailableToOtherTenants $True `
172 | -PublicClient $True
173 |
174 | # create the service principal of the newly created application
175 | $currentAppId = $clientAadApplication.AppId
176 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
177 |
178 | # add the user running the script as an app owner if needed
179 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId
180 | if ($owner -eq $null)
181 | {
182 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId
183 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'"
184 | }
185 |
186 |
187 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)"
188 |
189 | # URL of the AAD application in the Azure portal
190 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
191 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
192 | Add-Content -Value "client | $currentAppId | Console-Interactive-MultiTarget-v2 |
" -Path createdApps.html
193 |
194 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
195 |
196 | # Add Required Resources Access (from 'client' to 'Microsoft Graph')
197 | Write-Host "Getting access from 'client' to 'Microsoft Graph'"
198 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
199 | -requiredDelegatedPermissions "User.Read" `
200 |
201 | $requiredResourcesAccess.Add($requiredPermissions)
202 |
203 |
204 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess
205 | Write-Host "Granted permissions."
206 |
207 | # Update config file for 'client'
208 | $configFile = $pwd.Path + "\..\Console-Interactive-CustomWebUI\appsettings.json"
209 | Write-Host "Updating the sample code ($configFile)"
210 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId };
211 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary
212 |
213 | Add-Content -Value "
" -Path createdApps.html
214 | }
215 |
216 | # Pre-requisites
217 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) {
218 | Install-Module "AzureAD" -Scope CurrentUser
219 | }
220 |
221 | Import-Module AzureAD
222 |
223 | # Run interactively (will ask you for the tenant ID)
224 | ConfigureApplications -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/AppCreationScripts/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "Sample": {
3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API with custom web ui.",
4 | "Level": 300,
5 | "Client": ".NET Desktop (Console)",
6 | "Service": "Microsoft Graph",
7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial",
8 | "Endpoint": "AAD v2.0"
9 | },
10 |
11 | /*
12 | This section describes the Azure AD Applications to configure, and their dependencies
13 | */
14 | "AADApps": [
15 | {
16 | "Id": "client",
17 | "Name": "Console-Interactive-MultiTarget-v2",
18 | "Kind": "Desktop",
19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost",
20 | "RequiredResourcesAccess": [
21 | {
22 | "Resource": "Microsoft Graph",
23 | "DelegatedPermissions": [ "User.Read" ]
24 | }
25 | ]
26 | }
27 | ],
28 |
29 | /*
30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps
31 | are created in Azure AD.
32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location
33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value
34 | */
35 | "CodeConfiguration": [
36 | {
37 | "App": "client",
38 | "SettingKind": "JSon",
39 | "SettingFile": "\\..\\Console-Interactive-CustomWebUI\\appsettings.json",
40 | "Mappings": [
41 | {
42 | "key": "ClientId",
43 | "value": ".AppId"
44 | },
45 | {
46 | "key": "TenantId",
47 | "value": "$tenantId"
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/Console-Interactive-CustomWebUI/Console-Interactive-CustomWebUI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1; net472
6 | Console_Interactive_CustomWebUI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/Console-Interactive-CustomWebUI/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Graph;
3 | using Microsoft.Identity.Client;
4 | using System;
5 | using System.Linq;
6 | using System.Net.Http.Headers;
7 | using System.Threading.Tasks;
8 |
9 | namespace Console_Interactive_CustomWebUI
10 | {
11 | class Program
12 | {
13 | private static PublicClientApplicationOptions appConfiguration = null;
14 | private static IConfiguration configuration;
15 | private static string graphURL;
16 |
17 | // The MSAL Public client app
18 | private static IPublicClientApplication application;
19 |
20 | // Object with the custom HTML
21 | private static SystemWebViewOptions _customWebView = GetCustomHTML();
22 |
23 | static async Task Main(string[] args)
24 | {
25 | // Using appsettings.json as our configuration settings
26 | var builder = new ConfigurationBuilder()
27 | .SetBasePath(System.IO.Directory.GetCurrentDirectory())
28 | .AddJsonFile("appsettings.json");
29 |
30 | configuration = builder.Build();
31 |
32 | appConfiguration = configuration
33 | .Get();
34 |
35 | // We intend to obtain a token for Graph for the following scopes (permissions)
36 | string[] scopes = new[] { "user.read" };
37 |
38 | graphURL = configuration.GetValue("GraphApiUrl");
39 |
40 | // Sign-in user using MSAL and obtain an access token for MS Graph
41 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes);
42 |
43 | // Call the /me endpoint of MS Graph
44 | await CallMSGraph(graphClient);
45 |
46 | Console.ReadKey();
47 | }
48 |
49 | ///
50 | /// Sign in user using MSAL and obtain a token for MS Graph
51 | ///
52 | ///
53 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes)
54 | {
55 | GraphServiceClient graphClient = new GraphServiceClient(graphURL,
56 | new DelegateAuthenticationProvider(async (requestMessage) =>
57 | {
58 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes));
59 | }));
60 |
61 | return await Task.FromResult(graphClient);
62 | }
63 |
64 | ///
65 | /// Signs in the user using the device code flow and obtains an Access token for MS Graph
66 | ///
67 | ///
68 | ///
69 | ///
70 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes)
71 | {
72 | // build the AAd authority Url
73 | string authority = string.Concat(configuration.Instance, configuration.TenantId);
74 |
75 | // Initialize the MSAL library by building a public client application
76 | application = PublicClientApplicationBuilder.Create(configuration.ClientId)
77 | .WithAuthority(authority)
78 | .WithRedirectUri(configuration.RedirectUri)
79 | .Build();
80 |
81 |
82 | AuthenticationResult result;
83 |
84 | try
85 | {
86 | var accounts = await application.GetAccountsAsync();
87 |
88 | // Try to acquire an access token from the cache, if UI interaction is required, MsalUiRequiredException will be thrown.
89 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
90 | }
91 | catch (MsalUiRequiredException)
92 | {
93 | // Acquiring an access token interactively using the custom html.
94 | result = await application.AcquireTokenInteractive(scopes)
95 | .WithSystemWebViewOptions(_customWebView) // Using the custom html
96 | .ExecuteAsync();
97 | }
98 |
99 | return result.AccessToken;
100 | }
101 |
102 | ///
103 | /// Call MS Graph and print results
104 | ///
105 | ///
106 | ///
107 | private static async Task CallMSGraph(GraphServiceClient graphClient)
108 | {
109 | var me = await graphClient.Me.Request().GetAsync();
110 |
111 | // Printing the results
112 | Console.Write(Environment.NewLine);
113 | Console.WriteLine("-------- Data from call to MS Graph --------");
114 | Console.Write(Environment.NewLine);
115 | Console.WriteLine($"Id: {me.Id}");
116 | Console.WriteLine($"Display Name: {me.DisplayName}");
117 | Console.WriteLine($"Email: {me.Mail}");
118 | }
119 |
120 | ///
121 | /// Returns a custom HTML for the authorization success or failure, and redirect url.
122 | /// For more available options, please inspect the SystemWebViewOptions class.
123 | ///
124 | ///
125 | private static SystemWebViewOptions GetCustomHTML()
126 | {
127 | return new SystemWebViewOptions
128 | {
129 | HtmlMessageSuccess = @"
130 | Authentication Complete
131 |
132 |
133 | Custom Web UI
134 |
135 |
136 | Authentication complete
137 | You can return to the application. Feel free to close this browser tab.
138 |
139 |
140 |
141 | ",
142 |
143 | HtmlMessageError = @"
144 | Authentication Failed
145 |
146 |
147 | Custom Web UI
148 |
149 |
150 | Authentication failed
151 | Error details: error {0} error_description: {1}
152 |
153 | You can return to the application. Feel free to close this browser tab.
154 |
155 |
156 |
157 | "
158 | };
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/Console-Interactive-CustomWebUI/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Instance": "https://login.microsoftonline.com/",
3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
4 | "TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
5 | "RedirectUri": "http://localhost",
6 | "GraphApiUrl": "https://graph.microsoft.com/beta/"
7 | }
8 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/Console-Interactive.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29709.97
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console-Interactive-CustomWebUI", "Console-Interactive-CustomWebUI\Console-Interactive-CustomWebUI.csproj", "{509D5688-3BD1-4650-8A43-3CEF5B64198E}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {42EE218E-9522-4DBA-9DFE-2EC0FB0CD3D5}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/failureMessage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/failureMessage.png
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/successMessage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/successMessage.png
--------------------------------------------------------------------------------
/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-1-CustomHTML/ReadmeFiles/topology.png
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/AppCreationScripts.md:
--------------------------------------------------------------------------------
1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts
2 |
3 | ## Overview
4 |
5 | ### Quick summary
6 |
7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory
8 | 1. In PowerShell run:
9 | ```PowerShell
10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
11 | ```
12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below)
13 | ```PowerShell
14 | cd .\AppCreationScripts\
15 | .\Configure.ps1
16 | ```
17 | 1. Open the Visual Studio solution and click start
18 |
19 | ### More details
20 |
21 | The following paragraphs:
22 |
23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios.
24 | - Explain the [pre-requisites](#pre-requisites)
25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script):
26 | - [Interactively](#option-1-interactive) to create the app in your home tenant
27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant
28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant)
29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant)
30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds)
31 |
32 | ## Goal of the scripts
33 |
34 | ### Presentation of the scripts
35 |
36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra applications, and the configuration of the code for this sample. Once you run them, you will only need to build the solution and you are good to test.
37 |
38 | These scripts are:
39 |
40 | - `Configure.ps1` which:
41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets),
42 | - changes the configuration files in the C# and JavaScript projects.
43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created:
44 | - the identifier of the application
45 | - the AppId of the application
46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com).
47 |
48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset).
49 |
50 | ### Usage pattern for tests and DevOps scenarios
51 |
52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below.
53 |
54 | ## How to use the app creation scripts?
55 |
56 | ### Pre-requisites
57 |
58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window)
59 | 2. Navigate to the root directory of the project.
60 | 3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command:
61 | ```PowerShell
62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
63 | ```
64 | ### (Optionally) install AzureAD PowerShell modules
65 | The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps:
66 |
67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
68 |
69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator).
70 | 2. Type:
71 | ```PowerShell
72 | Install-Module AzureAD
73 | ```
74 |
75 | or if you cannot be administrator on your machine, run:
76 | ```PowerShell
77 | Install-Module AzureAD -Scope CurrentUser
78 | ```
79 |
80 | ### Run the script and start running
81 |
82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo,
83 | ```PowerShell
84 | cd AppCreationScripts
85 | ```
86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that.
87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**.
88 | 8. select **Start** for the projects
89 |
90 | You're done. this just works!
91 |
92 | ### Four ways to run the script
93 |
94 | We advise four ways of running the script:
95 |
96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects,
97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects,
98 | - Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects,
99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects.
100 |
101 | Here are the details on how to do this.
102 |
103 | #### Option 1 (interactive)
104 |
105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA).
106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined.
107 |
108 | Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in.
109 |
110 | #### Option 2 (non-interactive)
111 |
112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window
113 |
114 | ```PowerShell
115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
117 | . .\Cleanup.ps1 -Credential $mycreds
118 | . .\Configure.ps1 -Credential $mycreds
119 | ```
120 |
121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault.
122 |
123 | #### Option 3 (Interactive, but create apps in a specified tenant)
124 |
125 | if you want to create the apps in a particular tenant, you can use the following option:
126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com)
127 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window)
128 | - Find the "Active Directory" object in this tenant
129 | - Go to **Properties** and copy the content of the **Directory Id** property
130 | - Then use the full syntax to run the scripts:
131 |
132 | ```PowerShell
133 | $tenantId = "yourTenantIdGuid"
134 | . .\Cleanup.ps1 -TenantId $tenantId
135 | . .\Configure.ps1 -TenantId $tenantId
136 | ```
137 |
138 | #### Option 4 (non-interactive, and create apps in a specified tenant)
139 |
140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run:
141 |
142 | ```PowerShell
143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
145 | $tenantId = "yourTenantIdGuid"
146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId
147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId
148 | ```
149 |
150 | ### Running the script on Azure Sovereign clouds
151 |
152 | All the four options listed above, can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`.
153 |
154 | The acceptable values for this parameter are:
155 |
156 | - AzureCloud
157 | - AzureChinaCloud
158 | - AzureUSGovernment
159 | - AzureGermanyCloud
160 |
161 | Example:
162 |
163 | ```PowerShell
164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud"
165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud"
166 | ```
167 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/Cleanup.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) {
11 | Install-Module "AzureAD" -Scope CurrentUser
12 | }
13 | Import-Module AzureAD
14 | $ErrorActionPreference = "Stop"
15 |
16 | Function Cleanup
17 | {
18 | if (!$azureEnvironmentName)
19 | {
20 | $azureEnvironmentName = "AzureCloud"
21 | }
22 |
23 | <#
24 | .Description
25 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script
26 | #>
27 |
28 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
29 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
30 |
31 | # Login to Azure PowerShell (interactive if credentials are not already provided:
32 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
33 | if (!$Credential -and $TenantId)
34 | {
35 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
36 | }
37 | else
38 | {
39 | if (!$TenantId)
40 | {
41 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
42 | }
43 | else
44 | {
45 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
46 | }
47 | }
48 |
49 | if (!$tenantId)
50 | {
51 | $tenantId = $creds.Tenant.Id
52 | }
53 | $tenant = Get-AzureADTenantDetail
54 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name
55 |
56 | # Removes the applications
57 | Write-Host "Cleaning-up applications from tenant '$tenantName'"
58 |
59 | Write-Host "Removing 'client' (Console-Interactive-MultiTarget-v2) if needed"
60 | Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId }
61 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'"
62 | if ($apps)
63 | {
64 | Remove-AzureADApplication -ObjectId $apps.ObjectId
65 | }
66 |
67 | foreach ($app in $apps)
68 | {
69 | Remove-AzureADApplication -ObjectId $app.ObjectId
70 | Write-Host "Removed Console-Interactive-MultiTarget-v2.."
71 | }
72 | # also remove service principals of this app
73 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-Interactive-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false}
74 |
75 | }
76 |
77 | Cleanup -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/Configure.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | <#
11 | This script creates the Azure AD applications needed for this sample and updates the configuration files
12 | for the visual Studio projects from the data in the Azure AD applications.
13 |
14 | Before running this script you need to install the AzureAD cmdlets as an administrator.
15 | For this:
16 | 1) Run Powershell as an administrator
17 | 2) in the PowerShell window, type: Install-Module AzureAD
18 |
19 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
20 | #>
21 |
22 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
23 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
24 | # described in $permissionType
25 | Function AddResourcePermission($requiredAccess, `
26 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType)
27 | {
28 | foreach($permission in $requiredAccesses.Trim().Split("|"))
29 | {
30 | foreach($exposedPermission in $exposedPermissions)
31 | {
32 | if ($exposedPermission.Value -eq $permission)
33 | {
34 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
35 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
36 | $resourceAccess.Id = $exposedPermission.Id # Read directory data
37 | $requiredAccess.ResourceAccess.Add($resourceAccess)
38 | }
39 | }
40 | }
41 | }
42 |
43 | #
44 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"
45 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell
46 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal)
47 | {
48 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
49 | if ($servicePrincipal)
50 | {
51 | $sp = $servicePrincipal
52 | }
53 | else
54 | {
55 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
56 | }
57 | $appid = $sp.AppId
58 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
59 | $requiredAccess.ResourceAppId = $appid
60 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
61 |
62 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
63 | if ($requiredDelegatedPermissions)
64 | {
65 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
66 | }
67 |
68 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
69 | if ($requiredApplicationPermissions)
70 | {
71 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
72 | }
73 | return $requiredAccess
74 | }
75 |
76 |
77 | Function UpdateLine([string] $line, [string] $value)
78 | {
79 | $index = $line.IndexOf('=')
80 | $delimiter = ';'
81 | if ($index -eq -1)
82 | {
83 | $index = $line.IndexOf(':')
84 | $delimiter = ','
85 | }
86 | if ($index -ige 0)
87 | {
88 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter
89 | }
90 | return $line
91 | }
92 |
93 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
94 | {
95 | $lines = Get-Content $configFilePath
96 | $index = 0
97 | while($index -lt $lines.Length)
98 | {
99 | $line = $lines[$index]
100 | foreach($key in $dictionary.Keys)
101 | {
102 | if ($line.Contains($key))
103 | {
104 | $lines[$index] = UpdateLine $line $dictionary[$key]
105 | }
106 | }
107 | $index++
108 | }
109 |
110 | Set-Content -Path $configFilePath -Value $lines -Force
111 | }
112 |
113 | Set-Content -Value "" -Path createdApps.html
114 | Add-Content -Value "Application | AppId | Url in the Azure portal |
" -Path createdApps.html
115 |
116 | $ErrorActionPreference = "Stop"
117 |
118 | Function ConfigureApplications
119 | {
120 | <#.Description
121 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the
122 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config)
123 | so that they are consistent with the Applications parameters
124 | #>
125 | $commonendpoint = "common"
126 |
127 | if (!$azureEnvironmentName)
128 | {
129 | $azureEnvironmentName = "AzureCloud"
130 | }
131 |
132 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
133 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
134 |
135 | # Login to Azure PowerShell (interactive if credentials are not already provided:
136 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
137 | if (!$Credential -and $TenantId)
138 | {
139 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
140 | }
141 | else
142 | {
143 | if (!$TenantId)
144 | {
145 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
146 | }
147 | else
148 | {
149 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
150 | }
151 | }
152 |
153 | if (!$tenantId)
154 | {
155 | $tenantId = $creds.Tenant.Id
156 | }
157 |
158 |
159 |
160 | $tenant = Get-AzureADTenantDetail
161 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name
162 |
163 | # Get the user running the script to add the user as the app owner
164 | $user = Get-AzureADUser -ObjectId $creds.Account.Id
165 |
166 | # Create the client AAD application
167 | Write-Host "Creating the AAD application (Console-Interactive-MultiTarget-v2)"
168 | # create the application
169 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-Interactive-MultiTarget-v2" `
170 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient", "http://localhost" `
171 | -AvailableToOtherTenants $True `
172 | -PublicClient $True
173 |
174 | # create the service principal of the newly created application
175 | $currentAppId = $clientAadApplication.AppId
176 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
177 |
178 | # add the user running the script as an app owner if needed
179 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId
180 | if ($owner -eq $null)
181 | {
182 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId
183 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'"
184 | }
185 |
186 |
187 | Write-Host "Done creating the client application (Console-Interactive-MultiTarget-v2)"
188 |
189 | # URL of the AAD application in the Azure portal
190 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
191 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
192 | Add-Content -Value "client | $currentAppId | Console-Interactive-MultiTarget-v2 |
" -Path createdApps.html
193 |
194 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
195 |
196 | # Add Required Resources Access (from 'client' to 'Microsoft Graph')
197 | Write-Host "Getting access from 'client' to 'Microsoft Graph'"
198 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
199 | -requiredDelegatedPermissions "User.Read" `
200 |
201 | $requiredResourcesAccess.Add($requiredPermissions)
202 |
203 |
204 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess
205 | Write-Host "Granted permissions."
206 |
207 | # Update config file for 'client'
208 | $configFile = $pwd.Path + "\..\Console-Interactive-CustomWebUI\appsettings.json"
209 | Write-Host "Updating the sample code ($configFile)"
210 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId };
211 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary
212 |
213 | Add-Content -Value "
" -Path createdApps.html
214 | }
215 |
216 | # Pre-requisites
217 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) {
218 | Install-Module "AzureAD" -Scope CurrentUser
219 | }
220 |
221 | Import-Module AzureAD
222 |
223 | # Run interactively (will ask you for the tenant ID)
224 | ConfigureApplications -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/AppCreationScripts/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "Sample": {
3 | "Title": "Using the Microsoft identity platform to call Microsoft Graph API with custom web ui.",
4 | "Level": 300,
5 | "Client": ".NET Desktop (Console)",
6 | "Service": "Microsoft Graph",
7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial",
8 | "Endpoint": "AAD v2.0"
9 | },
10 |
11 | /*
12 | This section describes the Azure AD Applications to configure, and their dependencies
13 | */
14 | "AADApps": [
15 | {
16 | "Id": "client",
17 | "Name": "Console-Interactive-MultiTarget-v2",
18 | "Kind": "Desktop",
19 | "ReplyUrls": "https://login.microsoftonline.com/common/oauth2/nativeclient, http://localhost",
20 | "RequiredResourcesAccess": [
21 | {
22 | "Resource": "Microsoft Graph",
23 | "DelegatedPermissions": [ "User.Read" ]
24 | }
25 | ]
26 | }
27 | ],
28 |
29 | /*
30 | This section describes how to update the code in configuration files from the apps coordinates, once the apps
31 | are created in Azure AD.
32 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location
33 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value
34 | */
35 | "CodeConfiguration": [
36 | {
37 | "App": "client",
38 | "SettingKind": "JSon",
39 | "SettingFile": "\\..\\Console-Interactive-CustomWebUI\\appsettings.json",
40 | "Mappings": [
41 | {
42 | "key": "ClientId",
43 | "value": ".AppId"
44 | },
45 | {
46 | "key": "TenantId",
47 | "value": "$tenantId"
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-Core.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29709.97
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console-Interactive-CustomWebUI", "Console-Interactive-CustomWebUI\Console-Interactive-CustomWebUI.csproj", "{509D5688-3BD1-4650-8A43-3CEF5B64198E}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {509D5688-3BD1-4650-8A43-3CEF5B64198E}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {42EE218E-9522-4DBA-9DFE-2EC0FB0CD3D5}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/Console-Interactive-CustomWebUI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | Console_Interactive_CustomWebUI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/CustomWebBrowser/CustomBrowserWebUi.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Identity.Client.Extensibility;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Globalization;
6 | using System.Net;
7 | using System.Net.Sockets;
8 | using System.Runtime.InteropServices;
9 | using System.Text;
10 | using System.Threading;
11 | using System.Threading.Tasks;
12 | using System.Web;
13 |
14 | namespace Console_Interactive_CustomWebUI.CustomWebBrowser
15 | {
16 | internal class CustomBrowserWebUi : ICustomWebUi
17 | {
18 | //Success authentication html block
19 | private const string CloseWindowSuccessHtml = @"
20 | Authentication Complete
21 |
22 |
25 |
26 | Authentication complete
27 | You can return to the application. Feel free to close this browser tab.
28 |
29 |
30 |
31 | ";
32 |
33 | //Failure authentication html block
34 | private const string CloseWindowFailureHtml = @"
35 | Authentication Failed
36 |
37 |
40 |
41 | Authentication failed
42 | Error details: error {0} error_description: {1}
43 |
44 | You can return to the application. Feel free to close this browser tab.
45 |
46 |
47 |
48 | ";
49 |
50 | public async Task AcquireAuthorizationCodeAsync(
51 | Uri authorizationUri,
52 | Uri redirectUri,
53 | CancellationToken cancellationToken)
54 | {
55 | if (!redirectUri.IsLoopback)
56 | {
57 | throw new ArgumentException("Only loopback redirect uri is supported with this WebUI. Configure http://localhost or http://localhost:port during app registration. ");
58 | }
59 |
60 | Uri result = await InterceptAuthorizationUriAsync(authorizationUri,redirectUri,cancellationToken)
61 | .ConfigureAwait(true);
62 |
63 | return result;
64 | }
65 |
66 | public static string FindFreeLocalhostRedirectUri()
67 | {
68 | TcpListener listner = new TcpListener(IPAddress.Loopback, 0);
69 | try
70 | {
71 | listner.Start();
72 | int port = ((IPEndPoint)listner.LocalEndpoint).Port;
73 | return "http://localhost:" + port;
74 | }
75 | finally
76 | {
77 | listner?.Stop();
78 | }
79 | }
80 |
81 | private static void OpenBrowser(string url)
82 | {
83 | try
84 | {
85 | ProcessStartInfo psi = new ProcessStartInfo
86 | {
87 | FileName = url,
88 | UseShellExecute = true
89 | };
90 | Process.Start(psi);
91 | }
92 | catch
93 | {
94 | // hack because of this: https://github.com/dotnet/corefx/issues/10361
95 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
96 | {
97 | url = url.Replace("&", "^&");
98 | Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
99 | }
100 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
101 | {
102 | Process.Start("xdg-open", url);
103 | }
104 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
105 | {
106 | Process.Start("open", url);
107 | }
108 | else
109 | {
110 | throw new PlatformNotSupportedException(RuntimeInformation.OSDescription);
111 | }
112 | }
113 | }
114 |
115 | ///
116 | /// Opens a new tab on the OS default browser and navigates to the authorization URI, while listening to its response.
117 | /// Then, displays an HTML block based on the authorization response.
118 | ///
119 | private async Task InterceptAuthorizationUriAsync(
120 | Uri authorizationUri,
121 | Uri redirectUri,
122 | CancellationToken cancellationToken)
123 | {
124 | // Opens a browser sending the authorization request
125 | OpenBrowser(authorizationUri.ToString());
126 |
127 | // Listens to the localhost socket that opened the request
128 | using (var listener = new SingleMessageTcpListener(redirectUri.Port))
129 | {
130 | Uri authCodeUri = null;
131 | await listener.ListenToSingleRequestAndRespondAsync(
132 | (uri) =>
133 | {
134 | Trace.WriteLine("Intercepted an auth code url: " + uri.ToString());
135 | authCodeUri = uri;
136 |
137 | // Displays the success or failure HTML block based on the authorization response
138 | return GetMessageToShowInBroswerAfterAuth(uri);
139 | },
140 | cancellationToken)
141 | .ConfigureAwait(false);
142 |
143 | return authCodeUri;
144 | }
145 | }
146 |
147 | // Parses the authorization response and displays the success or failure HTML block accordingly
148 | private static string GetMessageToShowInBroswerAfterAuth(Uri uri)
149 | {
150 | // Parse the uri to understand if an error was returned. This is done just to show the user a nice error message in the browser.
151 | var authCodeQueryKeyValue = HttpUtility.ParseQueryString(uri.Query);
152 |
153 | string errorString = authCodeQueryKeyValue.Get("error");
154 | if (!string.IsNullOrEmpty(errorString))
155 | {
156 | return string.Format(
157 | CultureInfo.InvariantCulture,
158 | CloseWindowFailureHtml,
159 | errorString,
160 | authCodeQueryKeyValue.Get("error_description"));
161 | }
162 |
163 | return CloseWindowSuccessHtml;
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/CustomWebBrowser/SingleMessageTcpListener.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Net.Sockets;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Console_Interactive_CustomWebUI.CustomWebBrowser
11 | {
12 | internal class SingleMessageTcpListener : IDisposable
13 | {
14 | private readonly int _port;
15 | private readonly TcpListener _tcpListener;
16 |
17 | public SingleMessageTcpListener(int port)
18 | {
19 | if (port < 1 || port == 80)
20 | {
21 | throw new ArgumentOutOfRangeException("Expected a valid port number, > 0, not 80");
22 | }
23 |
24 | _port = port;
25 | _tcpListener = new TcpListener(IPAddress.Loopback, _port);
26 |
27 |
28 | }
29 |
30 | public async Task ListenToSingleRequestAndRespondAsync(Func responseProducer, CancellationToken cancellationToken)
31 | {
32 | cancellationToken.Register(() => _tcpListener.Stop());
33 | _tcpListener.Start();
34 |
35 | TcpClient tcpClient = null;
36 | try
37 | {
38 | tcpClient = await AcceptTcpClientAsync(cancellationToken)
39 | .ConfigureAwait(false);
40 |
41 | await ExtractUriAndRespondAsync(tcpClient, responseProducer, cancellationToken).ConfigureAwait(false);
42 |
43 | }
44 | finally
45 | {
46 | tcpClient?.Close();
47 | }
48 | }
49 |
50 | ///
51 | /// AcceptTcpClientAsync does not natively support cancellation, so use this wrapper. Make sure
52 | /// the cancellation token is registered to stop the listener.
53 | ///
54 | /// See https://stackoverflow.com/questions/19220957/tcplistener-how-to-stop-listening-while-awaiting-accepttcpclientasync
55 | private async Task AcceptTcpClientAsync(CancellationToken token)
56 | {
57 | try
58 | {
59 | return await _tcpListener.AcceptTcpClientAsync().ConfigureAwait(false);
60 | }
61 | catch (Exception ex) when (token.IsCancellationRequested)
62 | {
63 | throw new OperationCanceledException("Cancellation was requested while awaiting TCP client connection.", ex);
64 | }
65 | }
66 |
67 | private async Task ExtractUriAndRespondAsync(
68 | TcpClient tcpClient,
69 | Func responseProducer,
70 | CancellationToken cancellationToken)
71 | {
72 | cancellationToken.ThrowIfCancellationRequested();
73 |
74 | string httpRequest = await GetTcpResponseAsync(tcpClient, cancellationToken).ConfigureAwait(false);
75 | Uri uri = ExtractUriFromHttpRequest(httpRequest);
76 |
77 | // write an "OK, please close the browser message"
78 | await WriteResponseAsync(responseProducer(uri), tcpClient.GetStream(), cancellationToken)
79 | .ConfigureAwait(false);
80 | }
81 |
82 | #pragma warning disable CS1570 // XML comment has badly formed XML
83 | ///
84 | /// Example TCP response:
85 | ///
86 | /// {GET /?code=OAQABAAIAAAC5una0EUFgTIF8ElaxtWjTl5wse5YHycjcaO_qJukUUexKz660btJtJSiQKz1h4b5DalmXspKis-bS6Inu8lNs4CpoE4FITrLv00Mr3MEYEQzgrn6JiNoIwDFSl4HBzHG8Kjd4Ho65QGUMVNyTjhWyQDf_12E8Gw9sll_sbOU51FIreZlVuvsqIWBMIJ8mfmExZBSckofV6LbcKJTeEZKaqjC09x3k1dpsCNJAtYTQIus5g1DyhAW8viDpWDpQJlT55_0W4rrNKY3CSD5AhKd3Ng4_ePPd7iC6qObfmMBlCcldX688vR2IghV0GoA0qNalzwqP7lov-yf38uVZ3ir6VlDNpbzCoV-drw0zhlMKgSq6LXT7QQYmuA4RVy_7TE9gjQpW-P0_ZXUHirpgdsblaa3JUq4cXpbMU8YCLQm7I2L0oCkBTupYXKLoM2gHSYPJ5HChhj1x0pWXRzXdqbx_TPTujBLsAo4Skr_XiLQ4QPJZpkscmXezpPa5Z87gDenUBRBI9ppROhOksekMbvPataF0qBaM38QzcnzeOCFyih1OjIKsq3GeryChrEtfY9CL9lBZ6alIIQB4thD__Tc24OUmr04hX34PjMyt1Z9Qvr76Pw0r7A52JvqQLWupx8bqok6AyCwqUGfLCPjwylSLA7NYD7vScAbfkOOszfoCC3ff14Dqm3IAB1tUJfCZoab61c6Mozls74c2Ujr3roHw4NdPuo-re5fbpSw5RVu8MffWYwXrO3GdmgcvIMkli2uperucLldNVIp6Pc3MatMYSBeAikuhtaZiZAhhl3uQxzoMhU-MO9WXuG2oIkqSvKjghxi1NUhfTK4-du7I5h1r0lFh9b3h8kvE1WBhAIxLdSAA&state=b380f309-7d24-4793-b938-e4a512b2c7f6&session_state=a442c3cd-a25e-4b88-8b33-36d194ba11b2 HTTP/1.1
87 | /// Host: localhost:9001
88 | /// Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,ro;q=0.7,fr;q=0.6
89 | /// Connection: keep-alive
90 | /// Upgrade-Insecure-Requests: 1
91 | /// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
92 | /// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
93 | /// Accept-Encoding: gzip, deflate, br
94 | ///
95 | /// http://localhost:9001/?code=foo&session_state=bar
96 | private Uri ExtractUriFromHttpRequest(string httpRequest)
97 | #pragma warning restore CS1570 // XML comment has badly formed XML
98 | {
99 | string regexp = @"GET \/\?(.*) HTTP";
100 | string getQuery = null;
101 | Regex r1 = new Regex(regexp);
102 | Match match = r1.Match(httpRequest);
103 | if (!match.Success)
104 | {
105 | throw new InvalidOperationException("Not a GET query");
106 | }
107 |
108 | getQuery = match.Groups[1].Value;
109 | UriBuilder uriBuilder = new UriBuilder();
110 | uriBuilder.Query = getQuery;
111 | uriBuilder.Port = _port;
112 |
113 | return uriBuilder.Uri;
114 | }
115 |
116 | private static async Task GetTcpResponseAsync(TcpClient client, CancellationToken cancellationToken)
117 | {
118 | NetworkStream networkStream = client.GetStream();
119 |
120 | byte[] readBuffer = new byte[1024];
121 | StringBuilder stringBuilder = new StringBuilder();
122 | int numberOfBytesRead = 0;
123 |
124 | // Incoming message may be larger than the buffer size.
125 | do
126 | {
127 | numberOfBytesRead = await networkStream.ReadAsync(readBuffer, 0, readBuffer.Length, cancellationToken)
128 | .ConfigureAwait(false);
129 |
130 | string s = Encoding.ASCII.GetString(readBuffer, 0, numberOfBytesRead);
131 | stringBuilder.Append(s);
132 |
133 | }
134 | while (networkStream.DataAvailable);
135 |
136 | return stringBuilder.ToString();
137 | }
138 |
139 | private async Task WriteResponseAsync(string message, NetworkStream stream, CancellationToken cancellationToken)
140 | {
141 | string fullResponse = $"HTTP/1.1 200 OK\r\n\r\n{message}";
142 | var response = Encoding.ASCII.GetBytes(fullResponse);
143 | await stream.WriteAsync(response, 0, response.Length, cancellationToken).ConfigureAwait(false);
144 | await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
145 | }
146 |
147 | public void Dispose()
148 | {
149 | _tcpListener?.Stop();
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/Program.cs:
--------------------------------------------------------------------------------
1 | using Console_Interactive_CustomWebUI.CustomWebBrowser;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Graph;
4 | using Microsoft.Identity.Client;
5 | using Microsoft.Identity.Client.Extensibility;
6 | using System;
7 | using System.Linq;
8 | using System.Net.Http.Headers;
9 | using System.Threading.Tasks;
10 |
11 | namespace Console_Interactive_CustomWebUI
12 | {
13 | class Program
14 | {
15 | private static PublicClientApplicationOptions appConfiguration = null;
16 | private static IConfiguration configuration;
17 | private static string graphURL;
18 |
19 | // The MSAL Public client app
20 | private static IPublicClientApplication application;
21 |
22 | // Since the browser is started via Process.Start, there is no control over it,
23 | // So it is recommended to configure a timeout
24 | private const int TimeoutWaitingForBrowserMs = 30 * 1000; //30 seconds
25 |
26 | static async Task Main(string[] args)
27 | {
28 | // Using appsettings.json as our configuration settings
29 | var builder = new ConfigurationBuilder()
30 | .SetBasePath(System.IO.Directory.GetCurrentDirectory())
31 | .AddJsonFile("appsettings.json");
32 |
33 | configuration = builder.Build();
34 |
35 | appConfiguration = configuration
36 | .Get();
37 |
38 | string[] scopes = new[] { "user.read" };
39 |
40 | graphURL = configuration.GetValue("GraphApiUrl");
41 |
42 | // Sign-in user using MSAL and obtain an access token for MS Graph
43 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes);
44 |
45 | // Call the /me endpoint of MS Graph
46 | await CallMSGraph(graphClient);
47 |
48 | Console.ReadKey();
49 | }
50 |
51 | ///
52 | /// Sign in user using MSAL and obtain a token for MS Graph
53 | ///
54 | ///
55 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes)
56 | {
57 | GraphServiceClient graphClient = new GraphServiceClient(graphURL,
58 | new DelegateAuthenticationProvider(async (requestMessage) =>
59 | {
60 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes));
61 | }));
62 |
63 | return await Task.FromResult(graphClient);
64 | }
65 |
66 | ///
67 | /// Signs in the user using the device code flow and obtains an Access token for MS Graph
68 | ///
69 | ///
70 | ///
71 | ///
72 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes)
73 | {
74 | // build the AAd authority Url
75 | string authority = string.Concat(configuration.Instance, configuration.TenantId);
76 |
77 | // Initialize the MSAL library by building a public client application
78 | application = PublicClientApplicationBuilder.Create(configuration.ClientId)
79 | .WithAuthority(authority)
80 | .WithRedirectUri(CustomBrowserWebUi.FindFreeLocalhostRedirectUri()) // required for CustomBrowserWebUi
81 | .Build();
82 |
83 |
84 | AuthenticationResult result;
85 |
86 | try
87 | {
88 | var accounts = await application.GetAccountsAsync();
89 |
90 | // Try to acquire an access token from the cache, if UI interaction is required, MsalUiRequiredException will be thrown.
91 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
92 | }
93 | catch (MsalUiRequiredException)
94 | {
95 | // Acquiring an access token interactively using custom web UI.
96 | result = await application.AcquireTokenInteractive(scopes)
97 | .WithCustomWebUi(new CustomBrowserWebUi()) //Using our custom web ui
98 | .ExecuteAsync();
99 | }
100 |
101 | return result.AccessToken;
102 | }
103 |
104 | ///
105 | /// Call MS Graph and print results
106 | ///
107 | ///
108 | ///
109 | private static async Task CallMSGraph(GraphServiceClient graphClient)
110 | {
111 | var me = await graphClient.Me.Request().GetAsync();
112 |
113 | // Printing the results
114 | Console.Write(Environment.NewLine);
115 | Console.WriteLine("-------- Data from call to MS Graph --------");
116 | Console.Write(Environment.NewLine);
117 | Console.WriteLine($"Id: {me.Id}");
118 | Console.WriteLine($"Display Name: {me.DisplayName}");
119 | Console.WriteLine($"Email: {me.Mail}");
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/Console-Interactive-CustomWebUI/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Instance": "https://login.microsoftonline.com/",
3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
4 | "TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
5 | "RedirectUri": "http://localhost",
6 | "GraphApiUrl": "https://graph.microsoft.com/beta/"
7 | }
8 |
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/failureMessage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/failureMessage.png
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/successMessage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/successMessage.png
--------------------------------------------------------------------------------
/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/3-CustomWebUI/3-2-CustomBrowser/ReadmeFiles/topology.png
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/AppCreationScripts/AppCreationScripts.md:
--------------------------------------------------------------------------------
1 | # Registering the sample apps with Microsoft identity platform and updating the configuration files using PowerShell scripts
2 |
3 | ## Overview
4 |
5 | ### Quick summary
6 |
7 | 1. On Windows run PowerShell and navigate to the root of the cloned directory
8 | 1. In PowerShell run:
9 | ```PowerShell
10 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
11 | ```
12 | 1. Run the script to create your Microsoft Entra application and configure the code of the sample application accordingly. (Other ways of running the scripts are described below)
13 | ```PowerShell
14 | cd .\AppCreationScripts\
15 | .\Configure.ps1
16 | ```
17 | 1. Open the Visual Studio solution and click start
18 |
19 | ### More details
20 |
21 | The following paragraphs:
22 |
23 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios.
24 | - Explain the [pre-requisites](#pre-requisites)
25 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script):
26 | - [Interactively](#option-1-interactive) to create the app in your home tenant
27 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant
28 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant)
29 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant)
30 | - [Passing environment name, for Sovereign clouds](#running-the-script-on-azure-sovereign-clouds)
31 |
32 | ## Goal of the scripts
33 |
34 | ### Presentation of the scripts
35 |
36 | This sample comes with two PowerShell scripts, which automate the creation of the Microsoft Entra applications, and the configuration of the code for this sample. Once you run them, you will only need to build the solution and you are good to test.
37 |
38 | These scripts are:
39 |
40 | - `Configure.ps1` which:
41 | - creates Microsoft Entra applications and their related objects (permissions, dependencies, secrets),
42 | - changes the configuration files in the C# and JavaScript projects.
43 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Microsoft Entra application it created:
44 | - the identifier of the application
45 | - the AppId of the application
46 | - the url of its registration in the [Microsoft Entra admin center](https://entra.microsoft.com).
47 |
48 | - `Cleanup.ps1` which cleans-up the Microsoft Entra objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset).
49 |
50 | ### Usage pattern for tests and DevOps scenarios
51 |
52 | The `Configure.ps1` will stop if it tries to create a Microsoft Entra application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below.
53 |
54 | ## How to use the app creation scripts?
55 |
56 | ### Pre-requisites
57 |
58 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window)
59 | 2. Navigate to the root directory of the project.
60 | 3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command:
61 | ```PowerShell
62 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
63 | ```
64 | ### (Optionally) install AzureAD PowerShell modules
65 | The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps:
66 |
67 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this:
68 |
69 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator).
70 | 2. Type:
71 | ```PowerShell
72 | Install-Module AzureAD
73 | ```
74 |
75 | or if you cannot be administrator on your machine, run:
76 | ```PowerShell
77 | Install-Module AzureAD -Scope CurrentUser
78 | ```
79 |
80 | ### Run the script and start running
81 |
82 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo,
83 | ```PowerShell
84 | cd AppCreationScripts
85 | ```
86 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that.
87 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**.
88 | 8. select **Start** for the projects
89 |
90 | You're done. this just works!
91 |
92 | ### Four ways to run the script
93 |
94 | We advise four ways of running the script:
95 |
96 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects,
97 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects,
98 | - Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects,
99 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects.
100 |
101 | Here are the details on how to do this.
102 |
103 | #### Option 1 (interactive)
104 |
105 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA).
106 | - The script will be run as the signed-in user and will use the tenant in which the user is defined.
107 |
108 | Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in.
109 |
110 | #### Option 2 (non-interactive)
111 |
112 | When you know the identity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window
113 |
114 | ```PowerShell
115 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
116 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
117 | . .\Cleanup.ps1 -Credential $mycreds
118 | . .\Configure.ps1 -Credential $mycreds
119 | ```
120 |
121 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault.
122 |
123 | #### Option 3 (Interactive, but create apps in a specified tenant)
124 |
125 | if you want to create the apps in a particular tenant, you can use the following option:
126 | - open the [Microsoft Entra admin center](https://entra.microsoft.com)
127 | - Select the Microsoft Entra ID you are interested in (in the combo-box below your name on the top right of the browser window)
128 | - Find the "Active Directory" object in this tenant
129 | - Go to **Properties** and copy the content of the **Directory Id** property
130 | - Then use the full syntax to run the scripts:
131 |
132 | ```PowerShell
133 | $tenantId = "yourTenantIdGuid"
134 | . .\Cleanup.ps1 -TenantId $tenantId
135 | . .\Configure.ps1 -TenantId $tenantId
136 | ```
137 |
138 | #### Option 4 (non-interactive, and create apps in a specified tenant)
139 |
140 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run:
141 |
142 | ```PowerShell
143 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force
144 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd)
145 | $tenantId = "yourTenantIdGuid"
146 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId
147 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId
148 | ```
149 |
150 | ### Running the script on Azure Sovereign clouds
151 |
152 | All the four options listed above, can be used on any Azure Sovereign clouds. By default, the script targets `AzureCloud`, but it can be changed using the parameter `-AzureEnvironmentName`.
153 |
154 | The acceptable values for this parameter are:
155 |
156 | - AzureCloud
157 | - AzureChinaCloud
158 | - AzureUSGovernment
159 | - AzureGermanyCloud
160 |
161 | Example:
162 |
163 | ```PowerShell
164 | . .\Cleanup.ps1 -AzureEnvironmentName "AzureGermanyCloud"
165 | . .\Configure.ps1 -AzureEnvironmentName "AzureGermanyCloud"
166 | ```
167 |
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/AppCreationScripts/Cleanup.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | #Requires -Modules AzureAD
11 |
12 |
13 | if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) {
14 | Install-Module "AzureAD" -Scope CurrentUser
15 | }
16 | Import-Module AzureAD
17 | $ErrorActionPreference = "Stop"
18 |
19 | Function Cleanup
20 | {
21 | if (!$azureEnvironmentName)
22 | {
23 | $azureEnvironmentName = "AzureCloud"
24 | }
25 |
26 | <#
27 | .Description
28 | This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script
29 | #>
30 |
31 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
32 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
33 |
34 | # Login to Azure PowerShell (interactive if credentials are not already provided:
35 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
36 | if (!$Credential -and $TenantId)
37 | {
38 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
39 | }
40 | else
41 | {
42 | if (!$TenantId)
43 | {
44 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
45 | }
46 | else
47 | {
48 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
49 | }
50 | }
51 |
52 | if (!$tenantId)
53 | {
54 | $tenantId = $creds.Tenant.Id
55 | }
56 | $tenant = Get-AzureADTenantDetail
57 | $tenantName = ($tenant.VerifiedDomains | Where-Object { $_._Default -eq $True }).Name
58 |
59 | # Removes the applications
60 | Write-Host "Cleaning-up applications from tenant '$tenantName'"
61 |
62 | Write-Host "Removing 'client' (Console-DeviceCodeFlow-MultiTarget-v2) if needed"
63 | Get-AzureADApplication -Filter "DisplayName eq 'Console-DeviceCodeFlow-MultiTarget-v2'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId }
64 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'Console-DeviceCodeFlow-MultiTarget-v2'"
65 | if ($apps)
66 | {
67 | Remove-AzureADApplication -ObjectId $apps.ObjectId
68 | }
69 |
70 | foreach ($app in $apps)
71 | {
72 | Remove-AzureADApplication -ObjectId $app.ObjectId
73 | Write-Host "Removed Console-DeviceCodeFlow-MultiTarget-v2.."
74 | }
75 | # also remove service principals of this app
76 | Get-AzureADServicePrincipal -filter "DisplayName eq 'Console-DeviceCodeFlow-MultiTarget-v2'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false}
77 |
78 | }
79 |
80 | Cleanup -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/AppCreationScripts/Configure.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | param(
3 | [PSCredential] $Credential,
4 | [Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5 | [string] $tenantId,
6 | [Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script (it defaults to AzureCloud)')]
7 | [string] $azureEnvironmentName
8 | )
9 |
10 | #Requires -Modules AzureAD
11 |
12 | <#
13 | This script creates the Azure AD applications needed for this sample and updates the configuration files
14 | for the visual Studio projects from the data in the Azure AD applications.
15 |
16 | Before running this script you need to install the AzureAD cmdlets as an administrator.
17 | For this:
18 | 1) Run Powershell as an administrator
19 | 2) in the PowerShell window, type: Install-Module AzureAD
20 |
21 | There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
22 | #>
23 |
24 | # Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
25 | # The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
26 | # described in $permissionType
27 | Function AddResourcePermission($requiredAccess, `
28 | $exposedPermissions, [string]$requiredAccesses, [string]$permissionType)
29 | {
30 | foreach($permission in $requiredAccesses.Trim().Split("|"))
31 | {
32 | foreach($exposedPermission in $exposedPermissions)
33 | {
34 | if ($exposedPermission.Value -eq $permission)
35 | {
36 | $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
37 | $resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
38 | $resourceAccess.Id = $exposedPermission.Id # Read directory data
39 | $requiredAccess.ResourceAccess.Add($resourceAccess)
40 | }
41 | }
42 | }
43 | }
44 |
45 | #
46 | # Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"
47 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell
48 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal)
49 | {
50 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
51 | if ($servicePrincipal)
52 | {
53 | $sp = $servicePrincipal
54 | }
55 | else
56 | {
57 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
58 | }
59 | $appid = $sp.AppId
60 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
61 | $requiredAccess.ResourceAppId = $appid
62 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
63 |
64 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
65 | if ($requiredDelegatedPermissions)
66 | {
67 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
68 | }
69 |
70 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
71 | if ($requiredApplicationPermissions)
72 | {
73 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
74 | }
75 | return $requiredAccess
76 | }
77 |
78 |
79 | Function UpdateLine([string] $line, [string] $value)
80 | {
81 | $index = $line.IndexOf('=')
82 | $delimiter = ';'
83 | if ($index -eq -1)
84 | {
85 | $index = $line.IndexOf(':')
86 | $delimiter = ','
87 | }
88 | if ($index -ige 0)
89 | {
90 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter
91 | }
92 | return $line
93 | }
94 |
95 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
96 | {
97 | $lines = Get-Content $configFilePath
98 | $index = 0
99 | while($index -lt $lines.Length)
100 | {
101 | $line = $lines[$index]
102 | foreach($key in $dictionary.Keys)
103 | {
104 | if ($line.Contains($key))
105 | {
106 | $lines[$index] = UpdateLine $line $dictionary[$key]
107 | }
108 | }
109 | $index++
110 | }
111 |
112 | Set-Content -Path $configFilePath -Value $lines -Force
113 | }
114 |
115 | Set-Content -Value "" -Path createdApps.html
116 | Add-Content -Value "Application | AppId | Url in the Azure portal |
" -Path createdApps.html
117 |
118 | $ErrorActionPreference = "Stop"
119 |
120 | Function ConfigureApplications
121 | {
122 | <#.Description
123 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the
124 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config)
125 | so that they are consistent with the Applications parameters
126 | #>
127 | $commonendpoint = "common"
128 |
129 | if (!$azureEnvironmentName)
130 | {
131 | $azureEnvironmentName = "AzureCloud"
132 | }
133 |
134 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
135 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
136 |
137 | # Login to Azure PowerShell (interactive if credentials are not already provided:
138 | # you'll need to sign-in with creds enabling your to create apps in the tenant)
139 | if (!$Credential -and $TenantId)
140 | {
141 | $creds = Connect-AzureAD -TenantId $tenantId -AzureEnvironmentName $azureEnvironmentName
142 | }
143 | else
144 | {
145 | if (!$TenantId)
146 | {
147 | $creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
148 | }
149 | else
150 | {
151 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential -AzureEnvironmentName $azureEnvironmentName
152 | }
153 | }
154 |
155 | if (!$tenantId)
156 | {
157 | $tenantId = $creds.Tenant.Id
158 | }
159 |
160 |
161 |
162 | $tenant = Get-AzureADTenantDetail
163 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name
164 |
165 | # Get the user running the script to add the user as the app owner
166 | $user = Get-AzureADUser -ObjectId $creds.Account.Id
167 |
168 | # Create the client AAD application
169 | Write-Host "Creating the AAD application (Console-DeviceCodeFlow-MultiTarget-v2)"
170 | # create the application
171 | $clientAadApplication = New-AzureADApplication -DisplayName "Console-DeviceCodeFlow-MultiTarget-v2" `
172 | -ReplyUrls "https://login.microsoftonline.com/common/oauth2/nativeclient" `
173 | -PublicClient $True
174 |
175 | # create the service principal of the newly created application
176 | $currentAppId = $clientAadApplication.AppId
177 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
178 |
179 | # add the user running the script as an app owner if needed
180 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId
181 | if ($owner -eq $null)
182 | {
183 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId
184 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'"
185 | }
186 |
187 |
188 | Write-Host "Done creating the client application (Console-DeviceCodeFlow-MultiTarget-v2)"
189 |
190 | # URL of the AAD application in the Azure portal
191 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
192 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/"
193 | Add-Content -Value "client | $currentAppId | Console-DeviceCodeFlow-MultiTarget-v2 |
" -Path createdApps.html
194 |
195 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
196 |
197 | # Add Required Resources Access (from 'client' to 'Microsoft Graph')
198 | Write-Host "Getting access from 'client' to 'Microsoft Graph'"
199 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
200 | -requiredDelegatedPermissions "User.Read" `
201 |
202 | $requiredResourcesAccess.Add($requiredPermissions)
203 |
204 |
205 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess
206 | Write-Host "Granted permissions."
207 |
208 | # Update config file for 'client'
209 | $configFile = $pwd.Path + "\..\Console-DeviceCodeFlow-v2\appsettings.json"
210 | Write-Host "Updating the sample code ($configFile)"
211 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId;"TenantId" = $tenantId };
212 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary
213 |
214 | Add-Content -Value "
" -Path createdApps.html
215 | }
216 |
217 | # Pre-requisites
218 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) {
219 | Install-Module "AzureAD" -Scope CurrentUser
220 | }
221 |
222 | Import-Module AzureAD
223 |
224 | # Run interactively (will ask you for the tenant ID)
225 | ConfigureApplications -Credential $Credential -tenantId $TenantId
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/AppCreationScripts/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "Sample": {
3 | "Title": "Sign-in a user with the Microsoft identity platform using the device code flow and call Microsoft Graph on the user's behalf.",
4 | "Level": 100,
5 | "Client": ".NET Desktop (Console)",
6 | "Service": "Microsoft Graph",
7 | "RepositoryUrl": "ms-identity-dotnet-desktop-tutorial",
8 | "Endpoint": "AAD v2.0"
9 | },
10 |
11 | /*
12 | This section describes the Azure AD Applications to configure, and their dependencies
13 | */
14 | "AADApps": [
15 | {
16 | "Id": "client",
17 | "Name": "Console-DeviceCodeFlow-MultiTarget-v2",
18 | "Kind": "Desktop",
19 | "UsesROPCOrIWA": true,
20 | "Audience": "AzureADMyOrg",
21 | "RequiredResourcesAccess": [
22 | {
23 | "Resource": "Microsoft Graph",
24 | "DelegatedPermissions": [ "User.Read" ]
25 | }
26 | ]
27 | }
28 | ],
29 |
30 | /*
31 | This section describes how to update the code in configuration files from the apps coordinates, once the apps
32 | are created in Azure AD.
33 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location
34 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value
35 | */
36 | "CodeConfiguration": [
37 | {
38 | "App": "client",
39 | "SettingKind": "JSon",
40 | "SettingFile": "\\..\\Console-DeviceCodeFlow-v2\\appsettings.json",
41 | "Mappings": [
42 | {
43 | "key": "ClientId",
44 | "value": ".AppId"
45 | },
46 | {
47 | "key": "TenantId",
48 | "value": "$tenantId"
49 | }
50 | ]
51 | }
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/Console-DeviceCodeFlow-v2.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30011.22
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console-DeviceCodeFlow-v2", "Console-DeviceCodeFlow-v2\Console-DeviceCodeFlow-v2.csproj", "{23239B0D-FF39-43E7-998D-BCC885D918C2}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {23239B0D-FF39-43E7-998D-BCC885D918C2}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {04F72127-4CDB-4189-A854-4B2C7FD7D6C3}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/Console-DeviceCodeFlow-v2/Console-DeviceCodeFlow-v2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1; net472
6 | Console_DeviceCodeFlow_MultiTarget
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | PreserveNewest
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/Console-DeviceCodeFlow-v2/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Graph;
3 | using Microsoft.Identity.Client;
4 | using System;
5 | using System.Linq;
6 | using System.Net.Http.Headers;
7 | using System.Threading.Tasks;
8 |
9 | namespace Console_DeviceCodeFlow_MultiTarget
10 | {
11 | internal class Program
12 | {
13 | private static PublicClientApplicationOptions appConfiguration = null;
14 | private static IConfiguration configuration;
15 | private static string MSGraphURL;
16 |
17 | // The MSAL Public client app
18 | private static IPublicClientApplication application;
19 |
20 | private static async Task Main(string[] args)
21 | {
22 | // Using appsettings.json to load the configuration settings
23 | var builder = new ConfigurationBuilder()
24 | .SetBasePath(System.IO.Directory.GetCurrentDirectory())
25 | .AddJsonFile("appsettings.json");
26 |
27 | configuration = builder.Build();
28 |
29 | appConfiguration = configuration.Get();
30 |
31 | // We intend to obtain a token for Graph for the following scopes (permissions)
32 | string[] scopes = new[] { "user.read" };
33 |
34 | MSGraphURL = configuration.GetValue("GraphApiUrl");
35 |
36 | // Sign-in user using MSAL and obtain an access token for MS Graph
37 | GraphServiceClient graphClient = await SignInAndInitializeGraphServiceClient(appConfiguration, scopes);
38 |
39 | // Call the /me endpoint of MS Graph
40 | await CallMSGraph(graphClient);
41 | }
42 |
43 | ///
44 | /// Signs in the user using the device code flow and obtains an Access token for MS Graph
45 | ///
46 | ///
47 | ///
48 | ///
49 | private static async Task SignInUserAndGetTokenUsingMSAL(PublicClientApplicationOptions configuration, string[] scopes)
50 | {
51 | // build the AAd authority Url
52 | string authority = string.Concat(configuration.Instance, configuration.TenantId);
53 |
54 | // Initialize the MSAL library by building a public client application
55 | application = PublicClientApplicationBuilder.Create(configuration.ClientId)
56 | .WithAuthority(authority)
57 | .WithDefaultRedirectUri()
58 | .Build();
59 |
60 |
61 | AuthenticationResult result;
62 |
63 | try
64 | {
65 | var accounts = await application.GetAccountsAsync();
66 | // Try to acquire an access token from the cache. If device code is required, Exception will be thrown.
67 | result = await application.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
68 | }
69 | catch (MsalUiRequiredException)
70 | {
71 | result = await application.AcquireTokenWithDeviceCode(scopes, deviceCodeResult =>
72 | {
73 | // This will print the message on the console which tells the user where to go sign-in using
74 | // a separate browser and the code to enter once they sign in.
75 | // The AcquireTokenWithDeviceCode() method will poll the server after firing this
76 | // device code callback to look for the successful login of the user via that browser.
77 | // This background polling (whose interval and timeout data is also provided as fields in the
78 | // deviceCodeCallback class) will occur until:
79 | // * The user has successfully logged in via browser and entered the proper code
80 | // * The timeout specified by the server for the lifetime of this code (typically ~15 minutes) has been reached
81 | // * The developing application calls the Cancel() method on a CancellationToken sent into the method.
82 | // If this occurs, an OperationCanceledException will be thrown (see catch below for more details).
83 | Console.WriteLine(deviceCodeResult.Message);
84 | return Task.FromResult(0);
85 | }).ExecuteAsync();
86 | }
87 | return result.AccessToken;
88 | }
89 |
90 | ///
91 | /// Sign in user using MSAL and obtain a token for MS Graph
92 | ///
93 | ///
94 | private async static Task SignInAndInitializeGraphServiceClient(PublicClientApplicationOptions configuration, string[] scopes)
95 | {
96 | GraphServiceClient graphClient = new GraphServiceClient(MSGraphURL,
97 | new DelegateAuthenticationProvider(async (requestMessage) =>
98 | {
99 | requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await SignInUserAndGetTokenUsingMSAL(configuration, scopes));
100 | }));
101 |
102 | return await Task.FromResult(graphClient);
103 | }
104 |
105 | ///
106 | /// Call MS Graph and print results
107 | ///
108 | ///
109 | ///
110 | private static async Task CallMSGraph(GraphServiceClient graphClient)
111 | {
112 | var me = await graphClient.Me.Request().GetAsync();
113 |
114 | // Printing the results
115 | Console.Write(Environment.NewLine);
116 | Console.WriteLine("-------- Data from call to MS Graph --------");
117 | Console.Write(Environment.NewLine);
118 | Console.WriteLine($"Id: {me.Id}");
119 | Console.WriteLine($"Display Name: {me.DisplayName}");
120 | Console.WriteLine($"Email: {me.Mail}");
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/Console-DeviceCodeFlow-v2/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Instance": "https://login.microsoftonline.com/",
3 | "ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
4 | "TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
5 | "GraphApiUrl": "https://graph.microsoft.com/v1.0/"
6 | }
7 |
--------------------------------------------------------------------------------
/4-DeviceCodeFlow/ReadmeFiles/topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/233c37d4591a387c844a445ce16f3bc897241714/4-DeviceCodeFlow/ReadmeFiles/topology.png
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | languages:
4 | - csharp
5 | - powershell
6 | products:
7 | - azure
8 | - microsoft-entra-id
9 | - dotnet
10 | - aspnet
11 | - ms-graph
12 | description: "Learn how to acquire an access token on a console application."
13 | urlFragment: "ms-identity-dotnet-desktop-tutorial"
14 | ---
15 |
16 | 
17 |
18 | # Acquiring an access token using a console application and call APIs with the Microsoft identity platform for developers
19 |
20 | ## About this sample
21 |
22 | A multi-target console application (.Net Core and .Net Framework) that acquires an access token for a protected API on Azure, using Microsoft identity platform for developers. There are steps demonstrating this scenario on Microsoft Entra ID, Azure Active Directory B2C and National Clouds.
23 |
24 | On later steps, you will learn how to enrich the console application with a cross platform token cache and a custom Web UI (for .NET Core only).
25 |
26 | ## Structure of the repository
27 |
28 | This repository contains a progressive tutorial made of the following parts:
29 |
30 | | Sub folder | Description |
31 | | -------------------------------- | -------------------------------- |
32 | | [1. Calling Microsoft Graph](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/1-Calling-MSGraph) | This first part presents how to acquire an access token for Microsoft Graph, on Microsoft Entra ID, Azure B2C and Azure National Clouds. Each scenario is separated on its correspondent sub-folder.|
33 | | [2. Cross platform token cache](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/2-TokenCache) | This step shows how to configure a cross platform token cache (Windows, Linux and MAC) leveraging `Microsoft.Identity.Client.Extensions.Msal` |
34 | | [3. Custom Web UI](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/3-CustomWebUI) | This step shows how to customize the UI on the authorization response for a console application. |
35 | | [4. Device Code flow](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/tree/master/4-DeviceCodeFlow) | This step shows how to authenticate using device code flow.|
36 |
37 | ## Prerequisites
38 |
39 | - Install .NET Core for Windows by following the instructions at [dot.net/core](https://dot.net/core).
40 | - An Internet connection
41 | - a Microsoft Entra tenant. For more information on how to get a Microsoft Entra tenant, see [How to get a Microsoft Entra tenant](https://azure.microsoft.com/en-us/documentation/articles/active-directory-howto-tenant/)
42 | - A user account in your Microsoft Entra tenant.
43 |
44 | ## Setup
45 |
46 | From your shell or command line:
47 |
48 | ```Shell
49 | git clone https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial.git dotnet-desktop-tutorial
50 | cd dotnet-desktop-tutorial
51 | ```
52 |
53 | > Given that the name of the sample is pretty long, that it has sub-folders and so are the name of the referenced NuGet packages, you might want to clone it in a folder close to the root of your hard drive, to avoid file size limitations on Windows.
54 |
55 | ## Community Help and Support
56 |
57 | Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community.
58 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
59 | Make sure that your questions or comments are tagged with [`msal` `dotnet`].
60 |
61 | If you find a bug in the sample, please open an issue on [GitHub Issues](https://github.com/Azure-Samples/ms-identity-dotnet-desktop-tutorial/issues).
62 |
63 | To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory).
64 |
65 | ## Contributing
66 |
67 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md).
68 |
69 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
70 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------