├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── AppCreationScripts ├── AppCreationScripts.md ├── Cleanup.ps1 ├── Configure.ps1 └── sample.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── ReadmeFiles ├── Topology.png └── deviceCodeFlow.png ├── device-code-flow-console.sln └── device-code-flow-console ├── MyInformation.cs ├── Program.cs ├── ProtectedApiCallHelper.cs ├── PublicAppUsingDeviceCodeFlow.cs ├── SampleConfiguration.cs ├── appsettings.json └── device-code-flow-console.csproj /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /AppCreationScripts/AppCreationScripts.md: -------------------------------------------------------------------------------- 1 | # Registering the Azure Active Directory applications and updating the configuration files for this sample 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 Azure AD application and configure the code of the sample application accordinly. (Other ways of running the scripts are described below) 13 | ```PowerShell 14 | .\AppCreationScripts\Configure.ps1 15 | ``` 16 | 1. Open the Visual Studio solution and click start 17 | 18 | ### More details 19 | 20 | The following paragraphs: 21 | 22 | - [Present the scripts](#presentation-of-the-scripts) and explain their [usage patterns](#usage-pattern-for-tests-and-devops-scenarios) for test and DevOps scenarios. 23 | - Explain the [pre-requisites](#pre-requisites) 24 | - Explain [four ways of running the scripts](#four-ways-to-run-the-script): 25 | - [Interactively](#option-1-interactive) to create the app in your home tenant 26 | - [Passing credentials](#option-2-non-interactive) to create the app in your home tenant 27 | - [Interactively in a specific tenant](#option-3-interactive-but-create-apps-in-a-specified-tenant) 28 | - [Passing credentials in a specific tenant](#option-4-non-interactive-and-create-apps-in-a-specified-tenant) 29 | 30 | ## Goal of the scripts 31 | 32 | ### Presentation of the scripts 33 | 34 | This sample comes with two PowerShell scripts, which automate the creation of the Azure Active Directory applications, and the configuration of the code for this sample. Once you run them, you will only need to build the solution and you are good to test. 35 | 36 | These scripts are: 37 | 38 | - `Configure.ps1` which: 39 | - creates Azure AD applications and their related objects (permissions, dependencies, secrets), 40 | - changes the configuration files in the C# and JavaScript projects. 41 | - creates a summary file named `createdApps.html` in the folder from which you ran the script, and containing, for each Azure AD application it created: 42 | - the identifier of the application 43 | - the AppId of the application 44 | - the url of its registration in the [Azure portal](https://portal.azure.com). 45 | 46 | - `Cleanup.ps1` which cleans-up the Azure AD objects created by `Configure.ps1`. Note that this script does not revert the changes done in the configuration files, though. You will need to undo the change from source control (from Visual Studio, or from the command line using, for instance, git reset). 47 | 48 | ### Usage pattern for tests and DevOps scenarios 49 | 50 | The `Configure.ps1` will stop if it tries to create an Azure AD application which already exists in the tenant. For this, if you are using the script to try/test the sample, or in DevOps scenarios, you might want to run `Cleanup.ps1` just before `Configure.ps1`. This is what is shown in the steps below. 51 | 52 | ## How to use the app creation scripts ? 53 | 54 | ### Pre-requisites 55 | 56 | 1. Open PowerShell (On Windows, press `Windows-R` and type `PowerShell` in the search window) 57 | 2. Navigate to the root directory of the project. 58 | 3. Until you change it, the default [Execution Policy](https:/go.microsoft.com/fwlink/?LinkID=135170) for scripts is usually `Restricted`. In order to run the PowerShell script you need to set the Execution Policy to `RemoteSigned`. You can set this just for the current PowerShell process by running the command: 59 | ```PowerShell 60 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process 61 | ``` 62 | ### (Optionally) install AzureAD PowerShell modules 63 | The scripts install the required PowerShell module (AzureAD) for the current user if needed. However, if you want to install if for all users on the machine, you can follow the following steps: 64 | 65 | 4. If you have never done it already, in the PowerShell window, install the AzureAD PowerShell modules. For this: 66 | 67 | 1. Open PowerShell as admin (On Windows, Search Powershell in the search bar, right click on it and select Run as administrator). 68 | 2. Type: 69 | ```PowerShell 70 | Install-Module AzureAD 71 | ``` 72 | 73 | or if you cannot be administrator on your machine, run: 74 | ```PowerShell 75 | Install-Module AzureAD -Scope CurrentUser 76 | ``` 77 | 78 | ### Run the script and start running 79 | 80 | 5. Go to the `AppCreationScripts` sub-folder. From the folder where you cloned the repo, 81 | ```PowerShell 82 | cd AppCreationScripts 83 | ``` 84 | 6. Run the scripts. See below for the [four options](#four-ways-to-run-the-script) to do that. 85 | 7. Open the Visual Studio solution, and in the solution's context menu, choose **Set Startup Projects**. 86 | 8. select **Start** for the projects 87 | 88 | You're done. this just works! 89 | 90 | ### Four ways to run the script 91 | 92 | We advise four ways of running the script: 93 | 94 | - Interactive: you will be prompted for credentials, and the scripts decide in which tenant to create the objects, 95 | - non-interactive: you will provide credentials, and the scripts decide in which tenant to create the objects, 96 | - Interactive in specific tenant: you will provide the tenant in which you want to create the objects and then you will be prompted for credentials, and the scripts will create the objects, 97 | - non-interactive in specific tenant: you will provide tenant in which you want to create the objects and credentials, and the scripts will create the objects. 98 | 99 | Here are the details on how to do this. 100 | 101 | #### Option 1 (interactive) 102 | 103 | - Just run ``. .\Configure.ps1``, and you will be prompted to sign-in (email address, password, and if needed MFA). 104 | - The script will be run as the signed-in user and will use the tenant in which the user is defined. 105 | 106 | Note that the script will choose the tenant in which to create the applications, based on the user. Also to run the `Cleanup.ps1` script, you will need to re-sign-in. 107 | 108 | #### Option 2 (non-interactive) 109 | 110 | When you know the indentity and credentials of the user in the name of whom you want to create the applications, you can use the non-interactive approach. It's more adapted to DevOps. Here is an example of script you'd want to run in a PowerShell Window 111 | 112 | ```PowerShell 113 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 114 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 115 | . .\Cleanup.ps1 -Credential $mycreds 116 | . .\Configure.ps1 -Credential $mycreds 117 | ``` 118 | 119 | Of course, in real life, you might already get the password as a `SecureString`. You might also want to get the password from KeyVault. 120 | 121 | #### Option 3 (Interactive, but create apps in a specified tenant) 122 | 123 | if you want to create the apps in a particular tenant, you can use the following option: 124 | - open the [Azure portal](https://portal.azure.com) 125 | - Select the Azure Active directory you are interested in (in the combo-box below your name on the top right of the browser window) 126 | - Find the "Active Directory" object in this tenant 127 | - Go to **Properties** and copy the content of the **Directory Id** property 128 | - Then use the full syntax to run the scripts: 129 | 130 | ```PowerShell 131 | $tenantId = "yourTenantIdGuid" 132 | . .\Cleanup.ps1 -TenantId $tenantId 133 | . .\Configure.ps1 -TenantId $tenantId 134 | ``` 135 | 136 | #### Option 4 (non-interactive, and create apps in a specified tenant) 137 | 138 | This option combines option 2 and option 3: it creates the application in a specific tenant. See option 3 for the way to get the tenant Id. Then run: 139 | 140 | ```PowerShell 141 | $secpasswd = ConvertTo-SecureString "[Password here]" -AsPlainText -Force 142 | $mycreds = New-Object System.Management.Automation.PSCredential ("[login@tenantName here]", $secpasswd) 143 | $tenantId = "yourTenantIdGuid" 144 | . .\Cleanup.ps1 -Credential $mycreds -TenantId $tenantId 145 | . .\Configure.ps1 -Credential $mycreds -TenantId $tenantId 146 | ``` 147 | -------------------------------------------------------------------------------- /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 ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { 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 { $_._Default -eq $True }).Name 48 | 49 | # Removes the applications 50 | Write-Host "Cleaning-up applications from tenant '$tenantName'" 51 | 52 | Write-Host "Removing 'client' (active-directory-dotnet-deviceprofile) if needed" 53 | $app=Get-AzureADApplication -Filter "DisplayName eq 'active-directory-dotnet-deviceprofile'" 54 | 55 | if ($app) 56 | { 57 | Remove-AzureADApplication -ObjectId $app.ObjectId 58 | Write-Host "Removed." 59 | } 60 | 61 | } 62 | 63 | Cleanup -Credential $Credential -tenantId $TenantId 64 | -------------------------------------------------------------------------------- /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 | # Exemple: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read" 43 | # See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell 44 | Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal) 45 | { 46 | # If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique) 47 | if ($servicePrincipal) 48 | { 49 | $sp = $servicePrincipal 50 | } 51 | else 52 | { 53 | $sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'" 54 | } 55 | $appid = $sp.AppId 56 | $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess 57 | $requiredAccess.ResourceAppId = $appid 58 | $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess] 59 | 60 | # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application: 61 | if ($requiredDelegatedPermissions) 62 | { 63 | AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope" 64 | } 65 | 66 | # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application 67 | if ($requiredApplicationPermissions) 68 | { 69 | AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role" 70 | } 71 | return $requiredAccess 72 | } 73 | 74 | 75 | Function UpdateLine([string] $line, [string] $value) 76 | { 77 | $index = $line.IndexOf('=') 78 | $delimiter = ';' 79 | if ($index -eq -1) 80 | { 81 | $index = $line.IndexOf(':') 82 | $delimiter = ',' 83 | } 84 | if ($index -ige 0) 85 | { 86 | $line = $line.Substring(0, $index+1) + " "+'"'+$value+'"'+$delimiter 87 | } 88 | return $line 89 | } 90 | 91 | Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary) 92 | { 93 | $lines = Get-Content $configFilePath 94 | $index = 0 95 | while($index -lt $lines.Length) 96 | { 97 | $line = $lines[$index] 98 | foreach($key in $dictionary.Keys) 99 | { 100 | if ($line.Contains($key)) 101 | { 102 | $lines[$index] = UpdateLine $line $dictionary[$key] 103 | } 104 | } 105 | $index++ 106 | } 107 | 108 | Set-Content -Path $configFilePath -Value $lines -Force 109 | } 110 | 111 | Set-Content -Value "" -Path createdApps.html 112 | Add-Content -Value "" -Path createdApps.html 113 | 114 | Function ConfigureApplications 115 | { 116 | <#.Description 117 | This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the 118 | configuration files in the client and service project of the visual studio solution (App.Config and Web.Config) 119 | so that they are consistent with the Applications parameters 120 | #> 121 | 122 | # $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant 123 | # into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD. 124 | 125 | # Login to Azure PowerShell (interactive if credentials are not already provided: 126 | # you'll need to sign-in with creds enabling your to create apps in the tenant) 127 | if (!$Credential -and $TenantId) 128 | { 129 | $creds = Connect-AzureAD -TenantId $tenantId 130 | } 131 | else 132 | { 133 | if (!$TenantId) 134 | { 135 | $creds = Connect-AzureAD -Credential $Credential 136 | } 137 | else 138 | { 139 | $creds = Connect-AzureAD -TenantId $tenantId -Credential $Credential 140 | } 141 | } 142 | 143 | if (!$tenantId) 144 | { 145 | $tenantId = $creds.Tenant.Id 146 | } 147 | 148 | $tenant = Get-AzureADTenantDetail 149 | $tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name 150 | 151 | # Get the user running the script 152 | $user = Get-AzureADUser -ObjectId $creds.Account.Id 153 | 154 | # Create the client AAD application 155 | Write-Host "Creating the AAD application (active-directory-dotnet-deviceprofile)" 156 | $clientAadApplication = New-AzureADApplication -DisplayName "active-directory-dotnet-deviceprofile" ` 157 | -ReplyUrls "urn:ietf:wg:oauth:2.0:oob" ` 158 | -AvailableToOtherTenants $True ` 159 | -PublicClient $True 160 | 161 | $currentAppId = $clientAadApplication.AppId 162 | $clientServicePrincipal = New-AzureADServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp} 163 | 164 | # add the user running the script as an app owner if needed 165 | $owner = Get-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId 166 | if ($owner -eq $null) 167 | { 168 | Add-AzureADApplicationOwner -ObjectId $clientAadApplication.ObjectId -RefObjectId $user.ObjectId 169 | Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($clientServicePrincipal.DisplayName)'" 170 | } 171 | 172 | Write-Host "Done creating the client application (active-directory-dotnet-deviceprofile)" 173 | 174 | # URL of the AAD application in the Azure portal 175 | # Future? $clientPortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 176 | $clientPortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$clientAadApplication.AppId+"/objectId/"+$clientAadApplication.ObjectId+"/isMSAApp/" 177 | Add-Content -Value "" -Path createdApps.html 178 | 179 | $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess] 180 | 181 | # Add Required Resources Access (from 'client' to 'Microsoft Graph') 182 | Write-Host "Getting access from 'client' to 'Microsoft Graph'" 183 | $requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" ` 184 | -requiredDelegatedPermissions "User.Read|User.ReadBasic.All"; 185 | 186 | $requiredResourcesAccess.Add($requiredPermissions) 187 | 188 | 189 | Set-AzureADApplication -ObjectId $clientAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess 190 | Write-Host "Granted permissions." 191 | 192 | # Update config file for 'client' 193 | $configFile = $pwd.Path + "\..\device-code-flow-console\appsettings.json" 194 | Write-Host "Updating the sample code ($configFile)" 195 | $dictionary = @{ "ClientId" = $clientAadApplication.AppId }; 196 | UpdateTextFile -configFilePath $configFile -dictionary $dictionary 197 | 198 | Add-Content -Value "
ApplicationAppIdUrl in the Azure portal
client$currentAppIdactive-directory-dotnet-deviceprofile
" -Path createdApps.html 199 | } 200 | 201 | # Pre-requisites 202 | if ((Get-Module -ListAvailable -Name "AzureAD") -eq $null) { 203 | Install-Module "AzureAD" -Scope CurrentUser 204 | } 205 | Import-Module AzureAD 206 | 207 | # Run interactively (will ask you for the tenant ID) 208 | ConfigureApplications -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "Invoking an API protected by Azure AD from a text-only device", 4 | "Level": 200, 5 | "Client": ".NET Core 2.1 console app", 6 | "Service": "Microsoft Graph", 7 | "RepositoryUrl": "active-directory-dotnetcore-devicecodeflow-v2", 8 | "Endpoint": "AAD v2.0" 9 | }, 10 | 11 | /* 12 | This section describes the Azure AD Applications to configure, and their dependencies 13 | */ 14 | "AADApps": [ 15 | { 16 | "Id": "client", 17 | "Name": "active-directory-dotnet-deviceprofile", 18 | "Kind": "Desktop", 19 | "UsesROPCOrIWA": true, 20 | "Audience": "AzureADMultipleOrgs", 21 | "RequiredResourcesAccess": [ 22 | { 23 | "Resource": "Microsoft Graph", 24 | "DelegatedPermissions": [ "User.Read", "User.ReadBasic.All" ] 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": "\\..\\device-code-flow-console\\appsettings.json", 41 | "Mappings": [ 42 | { 43 | "key": "ClientId", 44 | "value": ".AppId" 45 | } 46 | ] 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 6 | 7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase main -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - csharp 5 | - powershell 6 | products: 7 | - azure-active-directory 8 | description: "This sample demonstrates how to leverage MSAL.NET from apps that do not have the capability of offering an interactive authentication experience." 9 | urlFragment: invoke-protected-api-text 10 | --- 11 | 12 | # Invoking an API protected by Microsoft identity platform from a text-only device 13 | 14 | [![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/AAD%20Samples/.NET%20client%20samples/active-directory-dotnetcore-devicecodeflow-v2-CI)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=684) 15 | 16 | > We have renamed the default branch to main. To rename your local repo follow the directions [here](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/renaming-a-branch#updating-a-local-clone-after-a-branch-name-changes). 17 | 18 | ## About this sample 19 | 20 | ### Overview 21 | 22 | This sample demonstrates how to leverage MSAL.NET from apps that **do not have the capability of offering an interactive authentication experience**. It enables these apps to: 23 | 24 | - authenticate a user 25 | - and call to a web API (in this case, the [Microsoft Graph](https://graph.microsoft.com)) 26 | 27 | The sample uses the OAuth2 **device code flow**. The app is built entirely on .NET Core, hence it can be ran as-is on Windows (including Nano Server), OSX, and Linux machines. 28 | 29 | To emulate a device not capable of showing UX, the sample is packaged as a .NET Core console application. 30 | The application signs users in with Azure Active Directory (Azure AD), using the Microsoft Authentication Library for .NET (MSAL.NET) to obtain a JWT access token through the OAuth 2.0 protocol. The access token is then used to call the Microsoft Graph API to obtain information about the user who signed-in. The sample is structured so that you can call your own API 31 | 32 | ![Topology](./ReadmeFiles/Topology.png) 33 | 34 | If you would like to get started immediately, skip this section and jump to *How To Run The Sample*. 35 | 36 | ### Scenario 37 | 38 | The application obtains tokens through a two steps process especially designed for devices and operating systems that cannot display any UX. Examples of such applications are applications running on iOT, or Command-Line tools (CLI). The idea is that: 39 | 40 | 1. whenever a user authentication is required, the command-line app provides a code and asks the user to use another device (such as an internet-connected smartphone) to navigate to [https://microsoft.com/devicelogin](https://microsoft.com/devicelogin), where the user will be prompted to enter the code. That done, the web page will lead the user through a normal authentication experience, including consent prompts and multi factor authentication if necessary. 41 | 42 | ![UI](./ReadmeFiles/deviceCodeFlow.png) 43 | 44 | 2. Upon successful authentication, the command-line app will receive the required tokens through a back channel and will use it to perform the web API calls it needs. In this case, the sample displays information about the user who signed-in and their manager. 45 | 46 | - Developers who wish to gain good familiarity of programming for Microsoft Graph are advised to go through the [An introduction to Microsoft Graph for developers](https://www.youtube.com/watch?v=EBbnpFdB92A) recorded session. 47 | 48 | ## About the code 49 | 50 | The code for handling the token acquisition process is simple, as it boils down to calling the `AcquireTokenWithDeviceCodeAsync` method of `PublicClientApplication` to which you pass a callback that will display information to the user about where they should navigate to, and which code to enter to initiate a sign-in. See the `GetTokenForWebApiUsingDeviceCodeFlowAsync` method in `device-code-flow-console\PublicAppUsingDeviceCodeFlow.cs`. 51 | 52 | ```CSharp 53 | async Task GetTokenForWebApiUsingDeviceCodeFlowAsync() 54 | 55 | AuthenticationResult result; 56 | try 57 | { 58 | result = await app.AcquireTokenWithDeviceCodeAsync(Scopes, 59 | deviceCodeCallback => 60 | { 61 | Console.WriteLine(deviceCodeCallback.Message); 62 | return Task.FromResult(0); 63 | }); 64 | } 65 | 66 | ... 67 | // error handling omitted here (see sample for details) 68 | return result; 69 | } 70 | ``` 71 | 72 | ## How to run this sample 73 | 74 | To run this sample, you'll need: 75 | 76 | - [Visual Studio 2019](https://aka.ms/vsdownload) or just the [.NET Core SDK](https://www.microsoft.com/net/learn/get-started). You will need the .NET Core 3.1 SDK. If you don't have it already, you can download from [Visual Studio SDKs](https://dotnet.microsoft.com/download/visual-studio-sdks) 77 | - An Internet connection 78 | - A Windows machine (necessary if you want to run the app on Windows) 79 | - An OS X machine (necessary if you want to run the app on Mac) 80 | - A Linux machine (necessary if you want to run the app on Linux) 81 | - An Azure Active Directory (Azure AD) tenant. For more information on how to get an Azure AD tenant, see [How to get an Azure AD tenant](https://azure.microsoft.com/en-us/documentation/articles/active-directory-howto-tenant/) 82 | - A user account in your Azure AD tenant. This sample will not work with a Microsoft account (formerly Windows Live account). Therefore, if you signed in to the [Azure portal](https://portal.azure.com) with a Microsoft account and have never created a user account in your directory before, you need to do that now. 83 | 84 | ### Step 1: Clone or download this repository 85 | 86 | From your shell or command line: 87 | 88 | ```Shell 89 | git clone https://github.com/Azure-Samples/active-directory-dotnetcore-devicecodeflow-v2.git 90 | ``` 91 | 92 | or download and extract the repository .zip file. 93 | 94 | > Given that the name of the sample is pretty long, and so are the name of the referenced NuGet packages, you might want to clone it in a folder close to the root of your hard drive, to avoid file size limitations on Windows. 95 | 96 | ### Step 2: Setup .NET Core 97 | 98 | The .NET Core [documentation pages](https://www.microsoft.com/net/learn/get-started) provide step by step instructions for installing .NET Core (the .NET Execution Environment) for your platform of choice. 99 | 100 | ### Step 3: Run the sample 101 | 102 | #### If you prefer to use Visual Studio 103 | 104 | Open the solution in Visual Studio, restore the NuGet packages, select the project, and start it in the debugger. 105 | 106 | #### (otherwise) on any platform 107 | 108 | Open a terminal and navigate to the project folder (`device-code-flow-console`). 109 | Restore the packages with the following command: 110 | 111 | ```PowerShell 112 | dotnet restore 113 | ``` 114 | 115 | Launch the app by entering the following command: 116 | 117 | ```PowerShell 118 | dotnet run 119 | ``` 120 | 121 | #### Operating the sample 122 | 123 | When you run the sample, you will be presented with a prompt telling you 124 | 125 | > To sign in, use a web browser to open the page https://microsoft.com/devicelogin. Enter the code B7D3SVXHV to authenticate. 126 | 127 | Then: 128 | 129 | 1. Open a browser on any device. For instance, the browser can be on the computer on which you are running the sample, or even your smartphone. Then navigate, as instructed, to [https://microsoft.com/devicelogin](https://microsoft.com/devicelogin) 130 | 131 | 2. Once there, type in the code provided by the app (in this sample, I am typing `B7D3SVXHV`) and hit enter. The web page will proceed to prompt you for authentication: please authenticate as a user (native or guest) in the tenant that you specified in the application. Note that, thanks to the fact that you are using an external browser or a different, browser capable device, you can authenticate without restrictions: for example, if your tenant requires you to authenticate using MFA, you are able to do so. That experience would not have been possible if you had to drive the authentication operations exclusively in the console. 132 | 3. Once you successfully authenticate, go back to the console app. You'll see that the app has now access to the token it needs to query the Microsoft Graph API and display information about the signed-in user. 133 | 134 | ### Optional: configure the sample as an app in your directory tenant 135 | 136 | The instructions so far leveraged the Azure AD entry for the app in a Microsoft test tenant: given that the app is multi-tenant, anybody can run the sample against that app entry. 137 | To register your project in your own Azure AD tenant, you can find instructions to manually provision the sample in your own tenant, so that you can exercise complete control on the app settings and behavior. 138 | 139 | - either follow the manual steps 140 | - or use PowerShell scripts that: 141 | - **automatically** creates the Azure AD applications and related objects (passwords, permissions, dependencies) for you 142 | - modify the Visual Studio projects' configuration files. 143 | 144 | If you want to use this automation: 145 | 1. On Windows run PowerShell and navigate to the root of the cloned directory 146 | 1. In PowerShell run: 147 | ```PowerShell 148 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 149 | ``` 150 | 1. Run the script to create your Azure AD application and configure the code of the sample application accordingly. 151 | ```PowerShell 152 | .\AppCreationScripts\Configure.ps1 153 | ``` 154 | > Other ways of running the scripts are described in [App Creation Scripts](./AppCreationScripts/AppCreationScripts.md) 155 | 156 | 1. Open the Visual Studio solution and click start 157 | 158 | If you don't want to use this automation, follow the steps below 159 | 160 | #### First step: choose the Azure AD tenant where you want to create your applications 161 | 162 | As a first step you'll need to: 163 | 164 | 1. Sign in to the [Azure portal](https://portal.azure.com) using either a work or school account or a personal Microsoft account. 165 | 1. If your account is present in more than one Azure AD tenant, select `Directory + Subscription` at the top right corner in the menu on top of the page, and switch your portal session to the desired Azure AD tenant. 166 | 1. In the left-hand navigation pane, select the **Azure Active Directory** service, and then select **App registrations**. 167 | 168 | > In the next steps, you might need the tenant name (or directory name) or the tenant ID (or directory ID). These are presented in the **Properties** 169 | of the Azure Active Directory window respectively as *Name* and *Directory ID* 170 | 171 | #### Register the client app (active-directory-dotnet-deviceprofile) 172 | 173 | 1. In **App registrations** page, select **New registration**. 174 | 1. When the **Register an application page** appears, enter your application's registration information: 175 | - In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `active-directory-dotnet-deviceprofile`. 176 | - In the **Supported account types** section, select **Accounts in any organizational directory**. 177 | 1. Select **Register** to create the application. 178 | 1. On the app **Overview** page, find the **Application (client) ID** value and record it for later. You'll need it to configure the Visual Studio configuration file for this project. 179 | 1. In the list of pages for the app, select **Manifest**, and: 180 | - In the manifest editor, set the ``allowPublicClient`` property to **true** 181 | - Select **Save** in the bar above the manifest editor. 182 | 1. In the list of pages for the app, select **API permissions** 183 | - Click the **Add a permission** button and then, 184 | - Ensure that the **Microsoft APIs** tab is selected 185 | - In the *Commonly used Microsoft APIs* section, click on **Microsoft Graph** 186 | - In the **Delegated permissions** section, ensure that the right permissions are checked: **User.Read**, **User.ReadBasic.All**. Use the search box if necessary. 187 | - Select the **Add permissions** button 188 | 189 | #### Configure the sample to use your Azure AD tenant 190 | 191 | In the steps below, ClientID is the same as Application ID or AppId. 192 | 193 | Open the solution in Visual Studio to configure the projects 194 | 195 | #### Configure the client project 196 | 197 | > Note: if you used the setup scripts, the changes below will have been applied for you 198 | 199 | 1. Open the `device-code-flow-console\appsettings.json` file 200 | 1. Find the app key `ClientId` and replace the existing value with the application ID (clientId) of the `active-directory-dotnet-deviceprofile` application copied from the Azure portal. 201 | 1. (Optional) If you created a single tenant application, find the line where `TenantId` is set and replace the existing value with your tenant ID. 202 | 203 | ## Community Help and Support 204 | 205 | Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. 206 | Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before. 207 | Make sure that your questions or comments are tagged with [`msal` `dotnet`]. 208 | 209 | If you find a bug in the sample, please raise the issue on [GitHub Issues](../../issues). 210 | 211 | To provide a recommendation, visit the following [User Voice page](https://feedback.azure.com/forums/169401-azure-active-directory). 212 | 213 | ## Contributing 214 | 215 | If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md). 216 | 217 | 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. 218 | 219 | ## More information 220 | 221 | For more information, see MSAL.NET's conceptual documentation: 222 | 223 | - [Device Code Flow for devices without a Web browser](https://aka.ms/msal-net-device-code-flow) 224 | - [Customizing Token cache serialization](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization) (was not done in this sample, but you might want to add a serialized cache) 225 | - [Quickstart: Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) 226 | - [Quickstart: Configure a client application to access web APIs](https://docs.microsoft.com/azure/active-directory/develop/quickstart-configure-app-access-web-apis) 227 | 228 | - [Understanding Azure AD application consent experiences](https://docs.microsoft.com/en-us/azure/active-directory/develop/application-consent-experience) 229 | - [Understand user and admin consent](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant#understand-user-and-admin-consent) 230 | - [Application and service principal objects in Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals) 231 | 232 | For more information about the Microsoft identity platform, see: 233 | - [https://aka.ms/aadv2](https://aka.ms/aadv2) 234 | -------------------------------------------------------------------------------- /ReadmeFiles/Topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-devicecodeflow-v2/90aa403705c574e2fd30ab0e7ec3973f291fce3b/ReadmeFiles/Topology.png -------------------------------------------------------------------------------- /ReadmeFiles/deviceCodeFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-devicecodeflow-v2/90aa403705c574e2fd30ab0e7ec3973f291fce3b/ReadmeFiles/deviceCodeFlow.png -------------------------------------------------------------------------------- /device-code-flow-console.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2041 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "device-code-flow-console", "device-code-flow-console\device-code-flow-console.csproj", "{2EA73CD2-D137-4C7F-94FA-314D4998E9B1}" 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 | {2EA73CD2-D137-4C7F-94FA-314D4998E9B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2EA73CD2-D137-4C7F-94FA-314D4998E9B1}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {2EA73CD2-D137-4C7F-94FA-314D4998E9B1}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {2EA73CD2-D137-4C7F-94FA-314D4998E9B1}.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 = {3EF5DD5E-765A-475C-A5AB-7B841BD66C54} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /device-code-flow-console/MyInformation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Identity.Client; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Threading.Tasks; 10 | 11 | namespace device_code_flow_console 12 | { 13 | /// 14 | /// MyInformation 15 | /// 16 | public class MyInformation 17 | { 18 | /// 19 | /// MyInformation ctor 20 | /// 21 | /// 22 | /// 23 | /// 24 | public MyInformation(IPublicClientApplication app, HttpClient client, string microsoftGraphBaseEndpoint) 25 | { 26 | tokenAcquisitionHelper = new PublicAppUsingDeviceCodeFlow(app); 27 | protectedApiCallHelper = new ProtectedApiCallHelper(client); 28 | this.MicrosoftGraphBaseEndpoint = microsoftGraphBaseEndpoint; 29 | } 30 | 31 | /// 32 | /// tokenAcquisitionHelper 33 | /// 34 | protected PublicAppUsingDeviceCodeFlow tokenAcquisitionHelper; 35 | 36 | /// 37 | /// protectedApiCallHelper 38 | /// 39 | protected ProtectedApiCallHelper protectedApiCallHelper; 40 | 41 | /// 42 | /// Scopes to request access to the protected web API (here Microsoft Graph) 43 | /// 44 | private static string[] Scopes { get; set; } = new string[] { "User.Read", "User.ReadBasic.All"}; 45 | 46 | /// 47 | /// Base endpoint for Microsoft Graph 48 | /// 49 | private string MicrosoftGraphBaseEndpoint { get; set; } 50 | 51 | /// 52 | /// URLs of the protected web APIs to call (here Microsoft Graph endpoints) 53 | /// 54 | private string WebApiUrlMe { get { return $"{MicrosoftGraphBaseEndpoint}/v1.0/me"; } } 55 | private string WebApiUrlMyManager { get { return $"{MicrosoftGraphBaseEndpoint}/v1.0/me/manager"; } } 56 | 57 | /// 58 | /// Calls the web API and displays its information 59 | /// 60 | /// 61 | public async Task DisplayMeAndMyManagerAsync() 62 | { 63 | AuthenticationResult authenticationResult = await tokenAcquisitionHelper.AcquireATokenFromCacheOrDeviceCodeFlowAsync(Scopes).ConfigureAwait(false); 64 | if (authenticationResult != null) 65 | { 66 | DisplaySignedInAccount(authenticationResult.Account); 67 | 68 | string accessToken = authenticationResult.AccessToken; 69 | await CallWebApiAndDisplayResultAsync(WebApiUrlMe, accessToken, "Me").ConfigureAwait(false); 70 | await CallWebApiAndDisplayResultAsync(WebApiUrlMyManager, accessToken, "My manager").ConfigureAwait(false); 71 | } 72 | } 73 | 74 | private static void DisplaySignedInAccount(IAccount account) 75 | { 76 | Console.ForegroundColor = ConsoleColor.Green; 77 | Console.WriteLine($"{account.Username} successfully signed-in"); 78 | } 79 | 80 | private async Task CallWebApiAndDisplayResultAsync(string url, string accessToken, string title) 81 | { 82 | Console.ForegroundColor = ConsoleColor.White; 83 | Console.WriteLine(title); 84 | Console.ResetColor(); 85 | await protectedApiCallHelper.CallWebApiAndProcessResultAsync(url, accessToken, Display).ConfigureAwait(false); 86 | Console.WriteLine(); 87 | } 88 | 89 | /// 90 | /// Display the result of the web API call 91 | /// 92 | /// Object to display 93 | private static void Display(JObject result) 94 | { 95 | foreach (JProperty child in result.Properties().Where(p => !p.Name.StartsWith('@'))) 96 | { 97 | Console.WriteLine($"{child.Name} = {child.Value}"); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /device-code-flow-console/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Identity.Client; 5 | using System; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | 9 | namespace device_code_flow_console 10 | { 11 | /// 12 | /// This sample signs-in a user in a two steps process: 13 | /// - it displays a URL and a code, and asks the user to navigate to the URL in a Web browser, and enter the code 14 | /// - then the user signs-in (and goes through multiple factor authentication if needed) 15 | /// and the sample displays information about the user by calling the Microsoft Graph in the name of the signed-in user 16 | /// 17 | /// It uses the Device code flow, which is normally used for devices which don't have a Web browser (which is the case for a 18 | /// .NET Core app, iOT, etc ...) 19 | /// 20 | /// For more information see https://aka.ms/msal-net-device-code-flow 21 | /// 22 | class Program 23 | { 24 | static void Main(string[] args) 25 | { 26 | try 27 | { 28 | RunAsync().GetAwaiter().GetResult(); 29 | } 30 | catch(Exception ex) 31 | { 32 | Console.ForegroundColor = ConsoleColor.Red; 33 | Console.WriteLine(ex.Message); 34 | Console.ResetColor(); 35 | } 36 | 37 | Console.WriteLine("Press any key to exit"); 38 | Console.ReadKey(); 39 | } 40 | 41 | private static async Task RunAsync() 42 | { 43 | SampleConfiguration config = SampleConfiguration.ReadFromJsonFile("appsettings.json"); 44 | var appConfig = config.PublicClientApplicationOptions; 45 | var app = PublicClientApplicationBuilder.CreateWithApplicationOptions(appConfig) 46 | .Build(); 47 | var httpClient = new HttpClient(); 48 | 49 | MyInformation myInformation = new MyInformation(app, httpClient, config.MicrosoftGraphBaseEndpoint); 50 | await myInformation.DisplayMeAndMyManagerAsync().ConfigureAwait(false); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /device-code-flow-console/ProtectedApiCallHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Net.Http.Headers; 10 | using System.Threading.Tasks; 11 | 12 | namespace device_code_flow_console 13 | { 14 | /// 15 | /// Helper class to call a protected API and process its result 16 | /// 17 | public class ProtectedApiCallHelper 18 | { 19 | /// 20 | /// Constructor 21 | /// 22 | /// HttpClient used to call the protected API 23 | public ProtectedApiCallHelper(HttpClient httpClient) 24 | { 25 | HttpClient = httpClient; 26 | } 27 | 28 | /// 29 | /// HttpClient 30 | /// 31 | protected HttpClient HttpClient { get; private set; } 32 | 33 | /// 34 | /// Calls the protected web API and processes the result 35 | /// 36 | /// URL of the web API to call (supposed to return Json) 37 | /// Access token used as a bearer security token to call the web API 38 | /// Callback used to process the result of the call to the web API 39 | public async Task CallWebApiAndProcessResultAsync(string webApiUrl, string accessToken, Action processResult) 40 | { 41 | if (!string.IsNullOrEmpty(accessToken)) 42 | { 43 | var defaultRequetHeaders = HttpClient.DefaultRequestHeaders; 44 | if (defaultRequetHeaders.Accept == null || !defaultRequetHeaders.Accept.Any(m => m.MediaType == "application/json")) 45 | { 46 | HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 47 | } 48 | defaultRequetHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken); 49 | 50 | HttpResponseMessage response = await HttpClient.GetAsync(webApiUrl).ConfigureAwait(false); 51 | if (response.IsSuccessStatusCode) 52 | { 53 | string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 54 | JObject result = JsonConvert.DeserializeObject(json) as JObject; 55 | Console.ForegroundColor = ConsoleColor.Gray; 56 | processResult(result); 57 | } 58 | else 59 | { 60 | Console.ForegroundColor = ConsoleColor.Red; 61 | string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 62 | 63 | if (!content.Contains("Resource 'manager' does not exist")) 64 | { 65 | Console.WriteLine($"Failed to call the Web Api: {response.StatusCode}"); 66 | Console.WriteLine($"Content: {content}"); 67 | } 68 | else 69 | { 70 | Console.ForegroundColor = ConsoleColor.Gray; 71 | Console.WriteLine("No manager"); 72 | } 73 | } 74 | Console.ResetColor(); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /device-code-flow-console/PublicAppUsingDeviceCodeFlow.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Identity.Client; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace device_code_flow_console 11 | { 12 | /// 13 | /// Security token provider using the Device Code flow 14 | /// 15 | public class PublicAppUsingDeviceCodeFlow 16 | { 17 | /// 18 | /// Constructor of a public application leveraging Device Code Flow to sign-in a user 19 | /// 20 | /// MSAL.NET Public client application 21 | /// 22 | /// For more information see https://aka.ms/msal-net-device-code-flow 23 | /// 24 | public PublicAppUsingDeviceCodeFlow(IPublicClientApplication app) 25 | { 26 | App = app; 27 | } 28 | 29 | /// 30 | /// IPublicClientApplication 31 | /// 32 | protected IPublicClientApplication App { get; private set; } 33 | 34 | /// 35 | /// Acquires a token from the token cache, or device code flow 36 | /// 37 | /// An AuthenticationResult if the user successfully signed-in, or otherwise null 38 | public async Task AcquireATokenFromCacheOrDeviceCodeFlowAsync(IEnumerable scopes) 39 | { 40 | AuthenticationResult result = null; 41 | var accounts = await App.GetAccountsAsync().ConfigureAwait(false); 42 | 43 | if (accounts.Any()) 44 | { 45 | try 46 | { 47 | // Attempt to get a token from the cache (or refresh it silently if needed) 48 | result = await App.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) 49 | .ExecuteAsync().ConfigureAwait(false); 50 | } 51 | catch (MsalUiRequiredException) 52 | { 53 | } 54 | } 55 | 56 | // Cache empty or no token for account in the cache, attempt by device code flow 57 | if (result == null) 58 | { 59 | result = await GetTokenForWebApiUsingDeviceCodeFlowAsync(scopes).ConfigureAwait(false); 60 | } 61 | 62 | return result; 63 | } 64 | 65 | /// 66 | /// Gets an access token so that the application accesses the web api in the name of the user 67 | /// who signs-in on a separate device 68 | /// 69 | /// An authentication result, or null if the user canceled sign-in, or did not sign-in on a separate device 70 | /// after a timeout (15 mins) 71 | private async Task GetTokenForWebApiUsingDeviceCodeFlowAsync(IEnumerable scopes) 72 | { 73 | AuthenticationResult result; 74 | try 75 | { 76 | result = await App.AcquireTokenWithDeviceCode(scopes, 77 | deviceCodeCallback => 78 | { 79 | // This will print the message on the console which tells the user where to go sign-in using 80 | // a separate browser and the code to enter once they sign in. 81 | // The AcquireTokenWithDeviceCodeAsync() method will poll the server after firing this 82 | // device code callback to look for the successful login of the user via that browser. 83 | // This background polling (whose interval and timeout data is also provided as fields in the 84 | // deviceCodeCallback class) will occur until: 85 | // * The user has successfully logged in via browser and entered the proper code 86 | // * The timeout specified by the server for the lifetime of this code (typically ~15 minutes) has been reached 87 | // * The developing application calls the Cancel() method on a CancellationToken sent into the method. 88 | // If this occurs, an OperationCanceledException will be thrown (see catch below for more details). 89 | Console.WriteLine(deviceCodeCallback.Message); 90 | return Task.FromResult(0); 91 | }).ExecuteAsync().ConfigureAwait(false); 92 | } 93 | catch (MsalServiceException ex) 94 | { 95 | // Kind of errors you could have (in errorCode and ex.Message) 96 | string errorCode = ex.ErrorCode; 97 | 98 | // AADSTS50059: No tenant-identifying information found in either the request or implied by any provided credentials. 99 | // Mitigation: as explained in the message from Azure AD, the authority needs to be tenanted. you have probably created 100 | // your public client application with the following authorities: 101 | // https://login.microsoftonline.com/common or https://login.microsoftonline.com/organizations 102 | 103 | // AADSTS90133: Device Code flow is not supported under /common or /consumers endpoint. 104 | // Mitigation: as explained in the message from Azure AD, the authority needs to be tenanted 105 | 106 | // AADSTS90002: Tenant not found. This may happen if there are 107 | // no active subscriptions for the tenant. Check with your subscription administrator. 108 | // Mitigation: if you have an active subscription for the tenant this might be that you have a typo in the 109 | // tenantId (GUID) or tenant domain name, update the 110 | 111 | // The issues above are typically programming / app configuration errors, they need to be fixed 112 | throw; 113 | } 114 | catch (OperationCanceledException) 115 | { 116 | // If you use an override with a CancellationToken, and call the Cancel() method on it, then this may be triggered 117 | // to indicate that the operation was cancelled. 118 | // See https://docs.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads 119 | // for more detailed information on how C# supports cancellation in managed threads. 120 | result = null; 121 | } 122 | catch (MsalClientException ex) 123 | { 124 | string errorCode = ex.ErrorCode; 125 | 126 | // Verification code expired before contacting the server 127 | // This exception will occur if the user does not manage to sign-in before a time out (15 mins) and the 128 | // call to `AcquireTokenWithDeviceCodeAsync` is not cancelled in between 129 | result = null; 130 | } 131 | return result; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /device-code-flow-console/SampleConfiguration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Identity.Client; 6 | using System.IO; 7 | using System.Reflection; 8 | 9 | namespace device_code_flow_console 10 | { 11 | /// 12 | /// Description of the configuration of an AzureAD public client application (desktop/mobile application). This should 13 | /// match the application registration done in the Azure portal 14 | /// 15 | public class SampleConfiguration 16 | { 17 | /// 18 | /// Authentication options 19 | /// 20 | public PublicClientApplicationOptions PublicClientApplicationOptions { get; set; } 21 | 22 | /// 23 | /// Base URL for Microsoft Graph (it varies depending on whether the application is ran 24 | /// in Microsoft Azure public clouds or national / sovereign clouds 25 | /// 26 | public string MicrosoftGraphBaseEndpoint { get; set; } 27 | 28 | /// 29 | /// Reads the configuration from a json file 30 | /// 31 | /// Path to the configuration json file 32 | /// SampleConfiguration as read from the json file 33 | public static SampleConfiguration ReadFromJsonFile(string path) 34 | { 35 | // .NET configuration 36 | IConfigurationRoot Configuration; 37 | 38 | var builder = new ConfigurationBuilder() 39 | .SetBasePath(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)) 40 | .AddJsonFile(path); 41 | 42 | Configuration = builder.Build(); 43 | // Read the auth and graph endpoint config 44 | SampleConfiguration config = new SampleConfiguration() 45 | { 46 | PublicClientApplicationOptions = new PublicClientApplicationOptions() 47 | }; 48 | Configuration.Bind("Authentication", config.PublicClientApplicationOptions); 49 | config.MicrosoftGraphBaseEndpoint = Configuration.GetValue("WebAPI:MicrosoftGraphBaseEndpoint"); 50 | return config; 51 | } 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /device-code-flow-console/appsettings.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/active-directory-dotnetcore-devicecodeflow-v2/90aa403705c574e2fd30ab0e7ec3973f291fce3b/device-code-flow-console/appsettings.json -------------------------------------------------------------------------------- /device-code-flow-console/device-code-flow-console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | device_code_flow_console 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------