├── .gitignore ├── AccountProviderSample ├── AccountProviderSample.sln └── AccountProviderSample │ ├── AccountProviderSample.csproj │ ├── App.config │ ├── CustomAccountCache.cs │ ├── CustomCacheItem.cs │ ├── CustomCacheKey.cs │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── README.md │ └── packages.config ├── CODE_OF_CONDUCT.md ├── ClientLibraryConsoleAppSample ├── App.config ├── ClientLibraryConsoleAppSample.csproj ├── ClientLibraryConsoleAppSample.sln ├── Program.cs └── README.md ├── ClientLibraryPatAppSample ├── App.config ├── ClientLibraryPatAppSample.csproj ├── ClientLibraryPatAppSample.sln ├── Program.cs └── README.md ├── DeviceProfileSample ├── App.config ├── DeviceProfileSample.csproj ├── DeviceProfileSample.sln ├── Program.cs └── README.md ├── DualSupportClientSample ├── App.config ├── DualSupportClientSample.csproj ├── DualSupportClientSample.sln ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── README.md ├── JavascriptWebAppSample ├── README.md ├── app.js ├── config.js ├── favicon.svg ├── fetch.js ├── index.html ├── package.json ├── server.js └── ui.js ├── ManagedClientConsoleAppSample ├── App.config ├── ManagedClientConsoleAppSample.csproj ├── ManagedClientConsoleAppSample.sln ├── Program.cs └── README.md ├── NonInteractivePatGenerationSample ├── NonInteractivePatGenerationSample.sln ├── NonInteractivePatGenerationSample │ ├── App.config │ ├── NonInteractivePatGenerationSample.csproj │ └── Program.cs └── README.md ├── OAuthWebSample ├── .gitignore ├── .vs │ └── config │ │ └── applicationhost.config ├── OAuthWebSample.sln ├── OAuthWebSample │ ├── App_Start │ │ ├── BundleConfig.cs │ │ ├── FilterConfig.cs │ │ └── RouteConfig.cs │ ├── Content │ │ ├── Site.css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap-theme.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ └── logo.png │ ├── Controllers │ │ ├── HomeController.cs │ │ └── OAuthController.cs │ ├── Global.asax │ ├── Global.asax.cs │ ├── Models │ │ └── TokenModel.cs │ ├── OAuthWebSample.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Scripts │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── jquery-3.4.1.intellisense.js │ │ ├── jquery-3.4.1.js │ │ ├── jquery-3.4.1.min.js │ │ ├── jquery-3.4.1.min.map │ │ ├── jquery-3.4.1.slim.js │ │ ├── jquery-3.4.1.slim.min.js │ │ ├── jquery-3.4.1.slim.min.map │ │ ├── jquery.validate-vsdoc.js │ │ ├── jquery.validate.js │ │ ├── jquery.validate.min.js │ │ ├── jquery.validate.unobtrusive.js │ │ ├── jquery.validate.unobtrusive.min.js │ │ └── modernizr-2.8.3.js │ ├── Views │ │ ├── Home │ │ │ ├── About.cshtml │ │ │ ├── Contact.cshtml │ │ │ └── Index.cshtml │ │ ├── OAuth │ │ │ └── TokenView.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ └── _Layout.cshtml │ │ ├── Web.config │ │ └── _ViewStart.cshtml │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Web.config │ ├── favicon.ico │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── packages.config ├── PublishScripts │ ├── AzureWebAppPublishModule.psm1 │ ├── Configurations │ │ └── OAuthSample-WAWS-dev.json │ └── Publish-WebApplication.ps1 ├── README.md └── appstart.png ├── PersonalAccessTokenAPIAppSample ├── .gitignore ├── AppCreationScripts │ ├── AppCreationScripts.md │ ├── Apps.json │ ├── Cleanup.ps1 │ ├── Configure.ps1 │ └── sample.json ├── README.md ├── ReadmeFiles │ └── topology.png ├── app.py ├── app_config.py ├── requirements.txt └── templates │ ├── auth_error.html │ ├── display.html │ ├── index.html │ └── login.html ├── README.md ├── SECURITY.md ├── ServicePrincipalsSamples ├── ClientLibsNET │ ├── .gitignore │ ├── 0-SimpleConsoleApp-AppRegistration │ │ ├── 0-SimpleConsoleApp-AppRegistration.csproj │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ └── README.md │ ├── 1-ConsoleApp-AppRegistration │ │ ├── 1-ConsoleApp-AppRegistration.csproj │ │ ├── Aad │ │ │ ├── AadAccessTokenHandler.cs │ │ │ └── AadClient.cs │ │ ├── AdoClient │ │ │ ├── AdoClientBase.cs │ │ │ ├── AdoConnection.cs │ │ │ ├── AdoRequestHandler.cs │ │ │ ├── AdoRestClient.cs │ │ │ ├── GraphClient.cs │ │ │ ├── MemberEntitlementsClient.cs │ │ │ ├── ProjectsClient.cs │ │ │ └── WorkItemsClient.cs │ │ ├── App.cs │ │ ├── AppMenu.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── README.md │ │ ├── Settings │ │ │ ├── AadConfiguration.cs │ │ │ ├── AdoConfiguration.cs │ │ │ ├── AppConfiguration.cs │ │ │ └── appsettings.json │ │ └── TableResult.cs │ ├── 2-ConsoleApp-ManagedIdentity │ │ ├── 2-ConsoleApp-ManagedIdentity.csproj │ │ ├── Program.cs │ │ └── README.md │ ├── 3-AzureFunction-ManagedIdentity │ │ ├── 3-AzureFunction-ManagedIdentity.csproj │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── README.md │ │ ├── TestMIHttpTrigger.cs │ │ ├── host.json │ │ └── local.settings.json │ ├── README.md │ └── ServicePrincipalsSamples.sln └── README.md ├── license.md └── samples.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | .vs/ 6 | _ReSharper.Caches/ 7 | 8 | *.suo 9 | packages/ 10 | 11 | *.csproj.user 12 | 13 | *.tmp 14 | bin/ 15 | obj/ 16 | 17 | app.Debug.config 18 | app.Release.config 19 | 20 | TestResults/ 21 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountProviderSample", "AccountProviderSample\AccountProviderSample.csproj", "{3EE4CED9-4158-468C-9F88-1035CA95A92C}" 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 | {3EE4CED9-4158-468C-9F88-1035CA95A92C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {3EE4CED9-4158-468C-9F88-1035CA95A92C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {3EE4CED9-4158-468C-9F88-1035CA95A92C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {3EE4CED9-4158-468C-9F88-1035CA95A92C}.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 = {44BC107C-DEA4-475C-A440-A57F48F818E1} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/CustomAccountCache.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using Microsoft.VisualStudio.Services.Client.AccountManagement; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace AccountProviderSample 9 | { 10 | public class CustomAccountCache : IAccountCache 11 | { 12 | // This is a simple in-memory cache. In a real scenario, you would want to use a persistent cache. 13 | private readonly ConcurrentDictionary inMemoryCache = new ConcurrentDictionary(); 14 | 15 | private string ClientId { get; } 16 | public string Authority { get; } 17 | 18 | public CustomAccountCache(string clientId, string authority) 19 | { 20 | ClientId = clientId; 21 | Authority = authority; 22 | } 23 | 24 | private CustomCacheKey ConstructKey(string[] scopes, string userIdentifier, string tenantId = null) 25 | { 26 | return new CustomCacheKey(scopes, userIdentifier, tenantId); 27 | } 28 | 29 | public async Task AcquireTokenInteractiveAsync(string[] scopes, Prompt prompt = default, string userIdentifier = null, string tenantId = null) 30 | { 31 | // This is a sub-optimal implementation, since the PublicApplicationClient could be reused. 32 | var builder = PublicClientApplicationBuilder.Create(ClientId).WithAuthority(Authority); 33 | 34 | var application = builder.Build(); 35 | var query = application.AcquireTokenInteractive(scopes); 36 | 37 | if (!string.IsNullOrWhiteSpace(userIdentifier)) 38 | { 39 | var account = await application.GetAccountAsync(userIdentifier); 40 | query.WithAccount(account); 41 | } 42 | 43 | if (!string.IsNullOrWhiteSpace(tenantId)) 44 | { 45 | query.WithTenantId(tenantId); 46 | } 47 | 48 | var result = await query.ExecuteAsync(); 49 | 50 | if (result != null) 51 | { 52 | var key = ConstructKey(scopes, userIdentifier, tenantId); 53 | var value = new CustomCacheItem(result); 54 | inMemoryCache.AddOrUpdate(key, value, (k, v) => value); 55 | return value; 56 | } 57 | 58 | return null; 59 | } 60 | 61 | public async Task AcquireTokenSilentAsync(string[] scopes, string userIdentifier, string tenantId = null) 62 | { 63 | var key = ConstructKey(scopes, userIdentifier, tenantId); 64 | if (inMemoryCache.TryGetValue(key, out var result)) 65 | { 66 | return result; 67 | } 68 | 69 | return await AcquireTokenInteractiveAsync(scopes, Prompt.NoPrompt, userIdentifier, tenantId); 70 | } 71 | 72 | public Task DeleteItemAsync(IAccountCacheItem token) 73 | { 74 | throw new NotImplementedException(); 75 | } 76 | 77 | public Task GetAnyUserIdentifierAsync() 78 | { 79 | throw new NotImplementedException(); 80 | } 81 | 82 | public IEnumerable GetItems() 83 | { 84 | throw new NotImplementedException(); 85 | } 86 | 87 | public Task> GetItemsAsync() 88 | { 89 | throw new NotImplementedException(); 90 | } 91 | 92 | public IEnumerable GetVsoEndpointToken(IAccountCacheItem tokenCacheItem) 93 | { 94 | throw new NotImplementedException(); 95 | } 96 | 97 | public Task> GetVsoEndpointTokenAsync(IAccountCacheItem tokenCacheItem) 98 | { 99 | throw new NotImplementedException(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/CustomCacheItem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using Microsoft.VisualStudio.Services.Client.AccountManagement; 3 | using System; 4 | 5 | namespace AccountProviderSample 6 | { 7 | public class CustomCacheItem : IAccountCacheItem 8 | { 9 | public CustomCacheItem(AuthenticationResult result) 10 | { 11 | UniqueId = result.UniqueId; 12 | TenantId = result.TenantId; 13 | Username = result.Account.Username; 14 | InnerResult = result; 15 | // ... 16 | } 17 | 18 | public string UniqueId { get; set; } 19 | 20 | public string TenantId { get; set; } 21 | 22 | public string Username { get; set; } 23 | 24 | public string Environment { get; set; } 25 | 26 | public string IdToken { get; set; } 27 | 28 | public DateTimeOffset ExpiresOn { get; set; } 29 | 30 | public string AccessToken { get; set; } 31 | 32 | public AuthenticationResult InnerResult { get; set; } 33 | 34 | public string GivenName { get; set; } 35 | 36 | public string FamilyName { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/CustomCacheKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AccountProviderSample 8 | { 9 | public struct CustomCacheKey 10 | { 11 | public CustomCacheKey(string[] scopes, string userIdentifier, string tenantId = null) 12 | { 13 | Scopes = scopes; 14 | UserIdentifier = userIdentifier; 15 | TenantId = tenantId; 16 | } 17 | 18 | public string[] Scopes { get; } 19 | public string UserIdentifier { get; } 20 | public string TenantId { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Services.Client.AccountManagement; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | 6 | namespace AccountProviderSample 7 | { 8 | internal class Program 9 | { 10 | private static string AadInstance = "https://login.microsoftonline.com/{0}"; // Change for other AAD instances 11 | private static string TenantId = "AAD_Tenant_ID"; 12 | private static string UserId = "User_UPN"; 13 | private static string ClientId = "AAD_Application_ID"; 14 | private static string Authority = String.Format(CultureInfo.InvariantCulture, AadInstance, TenantId); 15 | 16 | static void Main(string[] args) 17 | { 18 | var accountProvider = new VSAccountProvider(null); 19 | accountProvider.SetAccountCache(new CustomAccountCache(ClientId, Authority)); 20 | 21 | // This is the scope for the Azure DevOps Resource. Feel free to replace with your desired scopes. 22 | var scopes = new string[] { "499b84ac-1321-427f-aa17-267ca6975798/user_impersonation" }; 23 | 24 | // This is the handle of the window that will be used to display the authentication dialog. 25 | IntPtr handle = Process.GetCurrentProcess().MainWindowHandle; 26 | var accountKey = new AccountKey(UserId, Guid.Parse(TenantId)); 27 | var result = accountProvider.AcquireTokenAsync(scopes, TenantId, UserId, handle, accountKey).GetAwaiter().GetResult(); 28 | 29 | Console.WriteLine(result.AccessToken); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AccountProviderSample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corp.")] 12 | [assembly: AssemblyProduct("AccountProviderSample")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft Corp. 2023")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3ee4ced9-4158-468c-9f88-1035ca95a92c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/README.md: -------------------------------------------------------------------------------- 1 | # Account Provider Sample 2 | 3 | For native applications the best way to authenticate and access Azure DevOps resources is using the [Client Libraries](https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=vsts). They are .NET libraries made to simplify integration with Azure DevOps and Team Foundation Server (2015 and later). They allow access to both the Traditional Client Object Model and [new REST APIs](https://docs.microsoft.com/en-us/rest/api/vsts/?view=vsts-rest-4.1). 4 | 5 | ## Sample Application 6 | 7 | The Account Provider sample shows a simplified code flow for handling the account cache by the consumer. In combination with the ability to pass a UI handle inside the code, it is useful in desktop scenarios, e.g.: when working with Windows Forms apps. 8 | 9 | ## Step 1: Clone or download vsts-auth-samples repository 10 | 11 | From a shell or command line: 12 | ```no-highlight 13 | git clone https://github.com/Microsoft/vsts-auth-samples.git 14 | ``` 15 | 16 | ## Step 2: Run the sample 17 | 18 | 1. Navigate to the sample in cloned repo `vsts-auth-samples/AccountProviderSample/` 19 | 2. Use [Nuget package restore](https://docs.microsoft.com/en-us/nuget/consume-packages/package-restore) to ensure you have all dependencies installed 20 | 3. Open the solution file `AccountProviderSample.sln` in [Visual Studio](https://www.visualstudio.com/downloads/) 21 | 4. Open CS file `Program.cs` and change the input values at the top of the class: 22 | * `AadInstance` - the instance of Azure, for example public Azure or Azure China. 23 | * `TenantId` - the name or Id of the Azure AD tenant in which this application is registered. 24 | * `UserId` - user's UPN, usually an email address. 25 | * `ClientId` - used by the application to uniquely identify itself to Azure AD. 26 | 5. Additionally, you may want to change also the following variables: 27 | * `scopes` - the parts of target system you would like to get access to. 28 | * `handle` - the pointer to your main Window 29 | 6. Build and run solution. 30 | -------------------------------------------------------------------------------- /AccountProviderSample/AccountProviderSample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ClientLibraryConsoleAppSample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ClientLibraryConsoleAppSample/ClientLibraryConsoleAppSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net472 5 | 6 | true 7 | ClientLibraryConsoleAppSample 8 | ClientLibraryConsoleAppSample 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ClientLibraryConsoleAppSample/ClientLibraryConsoleAppSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientLibraryConsoleAppSample", "ClientLibraryConsoleAppSample.csproj", "{F7AC99CF-0972-4727-AC95-F537EF4EB4EA}" 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 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /ClientLibraryConsoleAppSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using Microsoft.TeamFoundation.WorkItemTracking.WebApi; 5 | using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; 6 | using Microsoft.VisualStudio.Services.Client; 7 | using Microsoft.VisualStudio.Services.WebApi; 8 | 9 | namespace ClientLibraryConsoleAppSample 10 | { 11 | class Program 12 | { 13 | //============= Config [Edit these with your settings] ===================== 14 | internal const string azureDevOpsOrganizationUrl = "https://dev.azure.com/organization"; //change to the URL of your Azure DevOps account; NOTE: This must use HTTPS 15 | // internal const string vstsCollectioUrl = "http://myserver:8080/tfs/DefaultCollection" alternate URL for a TFS collection 16 | //========================================================================== 17 | 18 | //Console application to execute a user defined work item query 19 | static void Main(string[] args) 20 | { 21 | //Prompt user for credential 22 | VssConnection connection = new VssConnection(new Uri(azureDevOpsOrganizationUrl), new VssClientCredentials()); 23 | 24 | //create http client and query for resutls 25 | WorkItemTrackingHttpClient witClient = connection.GetClient(); 26 | Wiql query = new Wiql() { Query = "SELECT [Id], [Title], [State] FROM workitems WHERE [Work Item Type] = 'Bug' AND [Assigned To] = @Me" }; 27 | WorkItemQueryResult queryResults = witClient.QueryByWiqlAsync(query).Result; 28 | 29 | //Display reults in console 30 | if (queryResults == null || queryResults.WorkItems.Count() == 0) 31 | { 32 | Console.WriteLine("Query did not find any results"); 33 | } 34 | else 35 | { 36 | foreach (var item in queryResults.WorkItems) 37 | { 38 | Console.WriteLine(item.Id); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ClientLibraryConsoleAppSample/README.md: -------------------------------------------------------------------------------- 1 | # Client Libraries Sample 2 | 3 | For native applications the best way to authenticate and access Azure DevOps resources is using the [Client Libraries](https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=vsts). They are .NET libraries made to simplify integration with Azure DevOps and Team Foundation Server (2015 and later). They allow access to both the Traditional Client Object Model and [new REST APIs](https://docs.microsoft.com/en-us/rest/api/vsts/?view=vsts-rest-4.1). 4 | 5 | ## Sample Application 6 | 7 | This buildable sample will walk you through the steps to create an application which uses the [Client Libraries](https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=vsts) to open an interactive login prompt and use that authentication state to execute a user pre-defined query written in [Work Item Query Language](https://msdn.microsoft.com/en-us/library/bb130198(v=vs.90).aspx). Query results are output into the console. 8 | 9 | ## Step 1: Clone or download vsts-auth-samples repository 10 | 11 | From a shell or command line: 12 | ```no-highlight 13 | git clone https://github.com/Microsoft/vsts-auth-samples.git 14 | ``` 15 | 16 | ## Step 2: Run the sample 17 | 18 | 1. Navigate to the sample in cloned repo `vsts-auth-samples/ClientLibraryConsoleAppSample/` 19 | 2. Use [Nuget package restore](https://docs.microsoft.com/en-us/nuget/consume-packages/package-restore) to ensure you have all dependencies installed 20 | 3. Open the solution file `ClientLibraryConsoleAppSample.csproj` in [Visual Studio 2017](https://www.visualstudio.com/downloads/) 21 | 4. Open CS file `Program.cs` and there is a section with input values to change at the top of the class: 22 | * `azureDevOpsOrganizationUrl` - Mutable value. This is the url to your Azure DevOps/TFS collection, e.g. http://dev.azure.com/organization for Azure DevOps or http://myserver:8080/tfs/DefaultCollection for TFS. 23 | 5. Build and run solution. After running you should see a list of the IDs all work items which match your query restrictions. 24 | -------------------------------------------------------------------------------- /ClientLibraryPatAppSample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ClientLibraryPatAppSample/ClientLibraryPatAppSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net472 5 | 6 | true 7 | ClientLibraryConsoleAppSample 8 | ClientLibraryConsoleAppSample 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ClientLibraryPatAppSample/ClientLibraryPatAppSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientLibraryPatAppSample", "ClientLibraryPatAppSample.csproj", "{F7AC99CF-0972-4727-AC95-F537EF4EB4EA}" 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 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F7AC99CF-0972-4727-AC95-F537EF4EB4EA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /ClientLibraryPatAppSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using Microsoft.TeamFoundation.WorkItemTracking.WebApi; 5 | using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; 6 | using Microsoft.VisualStudio.Services.Common; 7 | using Microsoft.VisualStudio.Services.WebApi; 8 | 9 | namespace ClientLibraryConsoleAppSample 10 | { 11 | class Program 12 | { 13 | //============= Config [Edit these with your settings] ===================== 14 | internal const string vstsCollectionUrl = "https://myaccount.visualstudio.com"; //change to the URL of your VSTS account; NOTE: This must use HTTPS 15 | // internal const string vstsCollectioUrl = "http://myserver:8080/tfs/DefaultCollection" alternate URL for a TFS collection 16 | internal const string pat = "PAT_GOES_HERE"; // change to the generated value 17 | //========================================================================== 18 | 19 | //Console application to execute a user defined work item query 20 | static void Main(string[] args) 21 | { 22 | //Prompt user for credential 23 | VssConnection connection = new VssConnection(new Uri(vstsCollectionUrl), new VssBasicCredential(string.Empty, pat)); 24 | 25 | //create http client and query for resutls 26 | WorkItemTrackingHttpClient witClient = connection.GetClient(); 27 | Wiql query = new Wiql() { Query = "SELECT [Id], [Title], [State] FROM workitems WHERE [Work Item Type] = 'Bug' AND [Assigned To] = @Me" }; 28 | WorkItemQueryResult queryResults = witClient.QueryByWiqlAsync(query).Result; 29 | 30 | //Display reults in console 31 | if (queryResults == null || queryResults.WorkItems.Count() == 0) 32 | { 33 | Console.WriteLine("Query did not find any results"); 34 | } 35 | else 36 | { 37 | foreach (var item in queryResults.WorkItems) 38 | { 39 | Console.WriteLine(item.Id); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ClientLibraryPatAppSample/README.md: -------------------------------------------------------------------------------- 1 | # Client Libraries Sample 2 | 3 | For native applications the best way to authenticate and access Azure DevOps resources is using the [Client Libraries](https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=vsts). They are .NET libraries made to simplify integration with Azure DevOps and Team Foundation Server (2015 and later). They allow access to both the Traditional Client Object Model and [new REST APIs](https://docs.microsoft.com/en-us/rest/api/vsts/?view=vsts-rest-4.1). 4 | 5 | ## Sample Application 6 | 7 | This buildable sample will walk you through the steps to create an application which uses the [Client Libraries](https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=vsts) and a personal access token to execute a user pre-defined query written in [Work Item Query Language](https://msdn.microsoft.com/en-us/library/bb130198(v=vs.90).aspx). Query results are output into the console. 8 | 9 | ## Step 1: Clone or download vsts-auth-samples repository 10 | 11 | From a shell or command line: 12 | ```no-highlight 13 | git clone https://github.com/Microsoft/vsts-auth-samples.git 14 | ``` 15 | 16 | ## Step 2: Run the sample 17 | 18 | 1. Generate a [PAT token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=vsts) and grant it "Work items (read)" permission and make a copy of the generated value 19 | 2. Navigate to the sample in cloned repo `vsts-auth-samples/ClientLibraryPatAppSample/` 20 | 3. Use [Nuget package restore](https://docs.microsoft.com/en-us/nuget/consume-packages/package-restore) to ensure you have all dependencies installed 21 | 4. Open the solution file `ClientLibraryPatAppSample.csproj` in [Visual Studio 2017](https://www.visualstudio.com/downloads/) 22 | 5. Open CS file `Program.cs` and there is a section with input values to change at the top of the class: 23 | * `azureDevOpsOrganizationUrl` - Mutable value. This is the url to your Azure DevOps/TFS organization/collection, e.g. http://dev.azure.com/organization for Azure DevOps or http://myserver:8080/tfs/DefaultCollection for TFS. 24 | * `pat` - Mutable value. This is the value you generated in step 1. 25 | 6. Build and run solution. After running you should see a list of the IDs all work items which match your query restrictions. 26 | -------------------------------------------------------------------------------- /DeviceProfileSample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /DeviceProfileSample/DeviceProfileSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net472 5 | true 6 | DeviceProfileSample 7 | DeviceProfileSample 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /DeviceProfileSample/DeviceProfileSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceProfileSample", "DeviceProfileSample.csproj", "{4B81F1A9-4A60-4793-ABEE-85546AAA480E}" 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 | {4B81F1A9-4A60-4793-ABEE-85546AAA480E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {4B81F1A9-4A60-4793-ABEE-85546AAA480E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {4B81F1A9-4A60-4793-ABEE-85546AAA480E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {4B81F1A9-4A60-4793-ABEE-85546AAA480E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /DualSupportClientSample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /DualSupportClientSample/DualSupportClientSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {755BC9EF-A095-48DC-BBF6-AF39F8C3363E} 8 | Exe 9 | Properties 10 | DualSupportClientSample 11 | DualSupportClientSample 12 | v4.7.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | DualSupportClientSample.Program 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Designer 57 | 58 | 59 | 60 | 61 | 4.46.1 62 | 63 | 64 | 19.212.0-preview 65 | 66 | 67 | 19.212.0-preview 68 | 69 | 70 | 13.0.2 71 | 72 | 73 | 74 | 81 | -------------------------------------------------------------------------------- /DualSupportClientSample/DualSupportClientSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DualSupportClientSample", "DualSupportClientSample.csproj", "{755BC9EF-A095-48DC-BBF6-AF39F8C3363E}" 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 | {755BC9EF-A095-48DC-BBF6-AF39F8C3363E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {755BC9EF-A095-48DC-BBF6-AF39F8C3363E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {755BC9EF-A095-48DC-BBF6-AF39F8C3363E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {755BC9EF-A095-48DC-BBF6-AF39F8C3363E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /DualSupportClientSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Services.Client; 3 | using Microsoft.VisualStudio.Services.WebApi; 4 | using Microsoft.TeamFoundation.Core.WebApi; 5 | using System.Collections.Generic; 6 | 7 | namespace DualSupportClientSample 8 | { 9 | //After running the console will close so please add a breakpoint or sleep to see output. 10 | class Program 11 | { 12 | //============= Config [Edit these with your settings] ===================== 13 | internal const string azureDevOpsOrganizationUrl = "https://dev.azure.com/organization"; //change to the URL of your Azure DevOps or TFS account; Azure DevOps: https://dev.azure.com/organization TFS: https://*:8080/tfs/defaultcollection" 14 | //========================================================================== 15 | 16 | public static void Main(string[] args) 17 | { 18 | try 19 | { 20 | //Based on collection URL will either start an interactive login session or use local Windows credential authentication 21 | VssConnection connection = new VssConnection(new Uri(azureDevOpsOrganizationUrl), new VssClientCredentials()); 22 | 23 | ProjectHttpClient projectClient = connection.GetClient(); 24 | IEnumerable projects = projectClient.GetProjects().Result; 25 | foreach (TeamProjectReference p in projects) 26 | { 27 | Console.WriteLine(p.Name); 28 | } 29 | } 30 | catch (Exception ex) 31 | { 32 | Console.WriteLine("{0}: {1}", ex.GetType(), ex.Message); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DualSupportClientSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DualSupportClientSample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DualSupportClientSample")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("755bc9ef-a095-48dc-bbf6-af39f8c3363e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /DualSupportClientSample/README.md: -------------------------------------------------------------------------------- 1 | # Dual Support (Azure DevOps/TFS) Client Sample 2 | 3 | For windows native applications which want to target both Azure DevOps and TFS we recommend using the [Client Libraries for Azure DevOps and TFS](https://docs.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=vsts) to generate interactive sign in prompts for Azure DevOps users and leverage seemless Windows credential authentication for TFS users. 4 | 5 | ## Sample Application 6 | 7 | This buildable sample will walk you through the steps to create a client-side console application which uses Client Libraries - Interactive and Windows Auth to authenticate a Azure DevOps or TFS user and return a list of all projects inside a selected Azure DevOps account or TFS collection. 8 | 9 | To run this sample you will need: 10 | * [Visual Studio IDE](https://www.visualstudio.com/vs/) 11 | * A [Azure DevOps organization](https://dev.azure.com/) 12 | 13 | ## Step 1: Clone or download vsts-auth-samples repository 14 | 15 | From a shell or command line: 16 | ```no-highlight 17 | git clone https://github.com/Microsoft/vsts-auth-samples.git 18 | ``` 19 | 20 | ## Step 2: Install and configure Client libraries (optional) 21 | 22 | Packages: [Microsoft.VisualStudio.Services.Client](https://www.nuget.org/packages/Microsoft.VisualStudio.Services.Client), and [Microsoft.TeamFoundation.Core.WebApi](https://www.nuget.org/packages/Microsoft.TeamFoundationServer.Client) have already been installed and configured in the sample, but if you are adding to your own project you will need to install and configure it yourself. 23 | 24 | ## Step 3: Run the sample 25 | 26 | 1. Navigate to the ADAL C# sample in cloned repo `vsts-auth-samples/DualSupportClientSample/`. 27 | 2. Open the solution file `VstsTfsSample.sln` in [Visual Studio 2017](https://www.visualstudio.com/downloads/). 28 | 3. Use [Nuget package restore](https://docs.microsoft.com/en-us/nuget/consume-packages/package-restore) to ensure you have all dependencies installed. 29 | 4. Open CS file `Program.cs` and there is a section with input values to change at the top of the class: 30 | * `azureDevOpsOrganizationUrl` - update this with the url to your Azure DevOps/TFS collection, e.g. http://dev.azure.com/organization for Azure DevOps or http://myserver:8080/tfs/DefaultCollection for TFS. 31 | 5. Build and run the solution. After running you should see an interactive login prompt if you are a Azure DevOps user. If you are a TFS user authentication should happen in the background. After authentication and authorization, a list of all projects inside of your account will be displayed in the console. 32 | 33 | -------------------------------------------------------------------------------- /JavascriptWebAppSample/README.md: -------------------------------------------------------------------------------- 1 | # Javascript Web App Sample 2 | 3 | For javascript web applications that want access to resources like Azure DevOps REST API, they will have to support an authentication flow for their users. The [Microsoft Authentication Library for JavaScript](https://github.com/AzureAD/microsoft-authentication-library-for-js) (MSAL.js) enables javascript application developers to setup interactive authentication flows and obtain access tokens for API usage. 4 | 5 | ## Sample Application 6 | 7 | This sample will walk you through the steps to create a single-page javascript application which uses **MSAL.js** to authenticate a user via an interactive prompt and display all projects contained in an Azure DevOps account/TFS collection. 8 | 9 | To run this sample you will need: 10 | 11 | - [Node.js](https://nodejs.org/en/download/) must be installed to run this sample. 12 | - A modern web browser. This sample uses **ES6** conventions and will not run on **Internet Explorer**. 13 | - [Visual Studio Code](https://code.visualstudio.com/download) is recommended for running and editing this sample. 14 | - 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/documentation/articles/active-directory-howto-tenant/) 15 | - A user account in your Azure AD tenant. 16 | - An Azure DevOps account backed by your AAD tenant where your user account has access. If you have an existing Azure DevOps account not connected to your AAD tenant follow these [steps to connect your AAD tenant to your Azure DevOps account](https://docs.microsoft.com/azure/devops/organizations/accounts/manage-azure-active-directory-groups-vsts?view=vsts&tabs=new-nav) 17 | 18 | ## Step 1: Clone or download azure-devops-auth-samples repository 19 | 20 | From a shell or command line: 21 | 22 | ```console 23 | git clone https://github.com/microsoft/azure-devops-auth-samples.git 24 | ``` 25 | 26 | Then locate the sample folder, where `package.json` file resides. Once you do, type: 27 | 28 | ```console 29 | npm install 30 | ``` 31 | 32 | ## Step 2: Register the app 33 | 34 | 1. Navigate to the Microsoft identity platform for developers [App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) page. 35 | 1. Select **New registration**. 36 | 1. In the **Register an application page** that appears, enter your application's registration information: 37 | - In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `devops-js-app`. 38 | - Under **Supported account types**, select **Accounts in your organizational directory only**. 39 | - In the **Redirect URI (optional)** section, select **Single-Page Application** in the combo-box and enter the following redirect URI: `http://localhost:8081/`. 40 | 1. Select **Register** to create the application. 41 | 1. In the app's registration screen, find and note the **Application (client) ID**. You use this value in your app's configuration file(s) later in your code. 42 | 1. Select **Save** to save your changes. 43 | 44 | ## Step 3: Configure the app to use your app registration 45 | 46 | Open the project in your IDE (like Visual Studio or Visual Studio Code) to configure the code. 47 | 48 | > In the steps below, "ClientID" is the same as "Application ID" or "AppId". 49 | 50 | 1. Open the `config.js` file 51 | 1. Find the key `Enter_the_Application_Id_Here` and replace it with the application ID (clientId) of the `devops-js-app` application copied from the Azure portal. 52 | 1. Find the key `Enter_the_Tenant_Id_Here` and replace it with the ID of your Azure AD tenant. 53 | 1. Find the key `Enter_the_Redirect_Uri_Here` and replace it with the base address of the `devops-js-app` project (by default `http://localhost:8081`). 54 | 1. Find the key `Enter_the_Tenant_Name_Here` and replace it with the name of your Azure AD tenant. 55 | 56 | ## Running the sample 57 | 58 | ```console 59 | npm start 60 | ``` 61 | 62 | ## Explore the sample 63 | 64 | 1. Open your browser and navigate to `http://localhost:8081`. 65 | 1. Click the **sign-in** button on the top right corner. 66 | 1. Once signed-in, select the **Call DevOps Rest API** button at the center. -------------------------------------------------------------------------------- /JavascriptWebAppSample/config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Configuration object to be passed to MSAL instance on creation. 4 | * For a full list of MSAL.js configuration parameters, visit: 5 | * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md 6 | */ 7 | 8 | const msalConfig = { 9 | auth: { 10 | clientId: "Enter_the_Application_Id_Here", // This is the ONLY mandatory field that you need to supply. 11 | authority: "https://login.microsoftonline.com/Enter_the_Tenant_Id_Here", // Defaults to "https://login.microsoftonline.com/common" 12 | redirectUri: "http://localhost:8081", // You must register this URI on Azure Portal/App Registration. Defaults to window.location.href 13 | postLogoutRedirectUri: "http://localhost:8081", // Simply remove this line if you would like navigate to index page after logout. 14 | navigateToLoginRequestUrl: false, // If "true", will navigate back to the original request location before processing the auth code response. 15 | }, 16 | cache: { 17 | cacheLocation: "sessionStorage", // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO btw tabs. 18 | storeAuthStateInCookie: false, // If you wish to store cache items in cookies as well as browser cache, set this to "true". 19 | }, 20 | system: { 21 | loggerOptions: { 22 | loggerCallback: (level, message, containsPii) => { 23 | if (containsPii) { 24 | return; 25 | } 26 | switch (level) { 27 | case msal.LogLevel.Error: 28 | console.error(message); 29 | return; 30 | case msal.LogLevel.Info: 31 | console.info(message); 32 | return; 33 | case msal.LogLevel.Verbose: 34 | console.debug(message); 35 | return; 36 | case msal.LogLevel.Warning: 37 | console.warn(message); 38 | return; 39 | } 40 | } 41 | } 42 | } 43 | }; 44 | 45 | // Add here the endpoints for services you would like to use. 46 | const apiConfig = { 47 | endpoint: "https://dev.azure.com/Enter_the_Tenant_Name_Here/_apis/projects?api-version=4.0", // e.g. https://dev.azure.com/fabrikam/_apis/projects?api-version=4.0 48 | scopes: ["499b84ac-1321-427f-aa17-267ca6975798/.default"] // 499b84ac-1321-427f-aa17-267ca6975798 is the App Id of Azure DevOps Service 49 | }; 50 | 51 | /** 52 | * Scopes you add here will be prompted for user consent during sign-in. 53 | * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request. 54 | * For more information about OIDC scopes, visit: 55 | * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes 56 | */ 57 | const loginRequest = { 58 | scopes: ["openid", "profile"], 59 | }; 60 | 61 | /** 62 | * Add here the scopes to request when obtaining an access token for a web API. For more information, see: 63 | * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md 64 | */ 65 | const tokenRequest = { 66 | scopes: apiConfig.scopes, 67 | forceRefresh: false // Set this to "true" to skip a cached token and go to the server to get a new token 68 | }; 69 | 70 | /** 71 | * An optional silentRequest object can be used to achieve silent SSO 72 | * between applications by providing a "login_hint" property. 73 | */ 74 | 75 | // const silentRequest = { 76 | // scopes: ["openid", "profile"], 77 | // loginHint: "example@domain.net" 78 | // }; 79 | -------------------------------------------------------------------------------- /JavascriptWebAppSample/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Icon-identity-221 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /JavascriptWebAppSample/fetch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function to call web API endpoint 3 | * using the authorization bearer token scheme 4 | */ 5 | function callApiWithToken(endpoint, token, callback) { 6 | const headers = new Headers(); 7 | const bearer = `Bearer ${token}`; 8 | 9 | headers.append("Authorization", bearer); 10 | 11 | const options = { 12 | method: "GET", 13 | headers: headers 14 | }; 15 | 16 | logMessage('Calling web API...'); 17 | 18 | fetch(endpoint, options) 19 | .then(response => response.json()) 20 | .then(response => { 21 | logMessage('Web API responds:'); 22 | logMessage(JSON.stringify(response.value[0], null, 4)); 23 | }).catch(error => { 24 | console.error(error); 25 | }); 26 | } -------------------------------------------------------------------------------- /JavascriptWebAppSample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Microsoft identity platform 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 28 |
29 |
JavaScript single-page application secured with MSAL.js
30 |
31 |
32 | 33 | 34 |
35 |
36 |
Sign-in with the Microsoft identity platform
37 |

38 |         
39 |       
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /JavascriptWebAppSample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msal-javascript-spa", 3 | "version": "1.0.0", 4 | "description": "JavaScript single-page application using MSAL.js to authenticate users against Azure Active Directory", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "keywords": [ 10 | "msal", 11 | "authentication", 12 | "ms-identity", 13 | "azure-ad" 14 | ], 15 | "author": "derisen", 16 | "license": "MIT", 17 | "dependencies": { 18 | "express": "^4.17.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /JavascriptWebAppSample/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | 4 | const DEFAULT_PORT = process.env.PORT || 8081; 5 | 6 | // initialize express. 7 | const app = express(); 8 | 9 | // Setup app folders. 10 | app.use(express.static('./')); 11 | 12 | // Set up a route for signout.html 13 | app.get('/signout', (req, res) => { 14 | res.sendFile(path.join(__dirname + '/App/signout.html')); 15 | }); 16 | 17 | // Set up a route for index.html 18 | app.get('*', (req, res) => { 19 | res.sendFile(path.join(__dirname + '/index.html')); 20 | }); 21 | 22 | // Start the server. 23 | app.listen(DEFAULT_PORT); 24 | console.log(`Listening on port ${DEFAULT_PORT}...`); 25 | -------------------------------------------------------------------------------- /JavascriptWebAppSample/ui.js: -------------------------------------------------------------------------------- 1 | // Select DOM elements to work with 2 | const signInButton = document.getElementById('signIn'); 3 | const signOutButton = document.getElementById('signOut') 4 | const titleDiv = document.getElementById('title-div'); 5 | const welcomeDiv = document.getElementById('welcome-div'); 6 | const tableDiv = document.getElementById('table-div'); 7 | const tableBody = document.getElementById('table-body-div'); 8 | const callApiButton = document.getElementById('callApiButton'); 9 | const response = document.getElementById("response"); 10 | const label = document.getElementById('label'); 11 | 12 | function welcomeUser(username) { 13 | label.classList.add('d-none'); 14 | signInButton.classList.add('d-none'); 15 | signOutButton.classList.remove('d-none'); 16 | titleDiv.classList.add('d-none'); 17 | welcomeDiv.classList.remove('d-none'); 18 | welcomeDiv.innerHTML = `Welcome ${username}!` 19 | callApiButton.classList.remove('d-none'); 20 | } 21 | 22 | function logMessage(s) { 23 | response.appendChild(document.createTextNode('\n' + s + '\n')); 24 | } -------------------------------------------------------------------------------- /ManagedClientConsoleAppSample/ManagedClientConsoleAppSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net472 5 | true 6 | ManagedClientConsoleAppSample 7 | ManagedClientConsoleAppSample 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ManagedClientConsoleAppSample/ManagedClientConsoleAppSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedClientConsoleAppSample", "ManagedClientConsoleAppSample.csproj", "{C2207AD3-D1A1-4F39-A788-9CFD27221DDD}" 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 | {C2207AD3-D1A1-4F39-A788-9CFD27221DDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {C2207AD3-D1A1-4F39-A788-9CFD27221DDD}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {C2207AD3-D1A1-4F39-A788-9CFD27221DDD}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {C2207AD3-D1A1-4F39-A788-9CFD27221DDD}.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 = {AC03DA2B-F3A2-46D5-8DE9-E5BE2A04F180} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /NonInteractivePatGenerationSample/NonInteractivePatGenerationSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2005 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NonInteractivePatGenerationSample", "NonInteractivePatGenerationSample\NonInteractivePatGenerationSample.csproj", "{2A8F9A96-2FB4-4FCA-A67C-44C757D354E3}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EF421301-2EA7-486F-A4FD-275164554E00}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {2A8F9A96-2FB4-4FCA-A67C-44C757D354E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {2A8F9A96-2FB4-4FCA-A67C-44C757D354E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {2A8F9A96-2FB4-4FCA-A67C-44C757D354E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {2A8F9A96-2FB4-4FCA-A67C-44C757D354E3}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {8E431534-F27F-4744-BB8B-33880BB3ED0F} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /NonInteractivePatGenerationSample/NonInteractivePatGenerationSample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /NonInteractivePatGenerationSample/NonInteractivePatGenerationSample/NonInteractivePatGenerationSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net472 5 | 6 | true 7 | NonInteractivePatGenerationSample 8 | NonInteractivePatGenerationSample 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /NonInteractivePatGenerationSample/NonInteractivePatGenerationSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Globalization; 4 | using System.Threading.Tasks; 5 | using Microsoft.Identity.Client; 6 | using Microsoft.VisualStudio.Services.Client; 7 | using Microsoft.VisualStudio.Services.DelegatedAuthorization; 8 | using Microsoft.VisualStudio.Services.DelegatedAuthorization.Client; 9 | using Microsoft.VisualStudio.Services.WebApi; 10 | 11 | namespace NonInteractivePatGenerationSample 12 | { 13 | public static class Program 14 | { 15 | internal static string[] scopes = new string[] { "499b84ac-1321-427f-aa17-267ca6975798/user_impersonation" }; //Constant value to target Azure DevOps. Do not change 16 | 17 | // The Azure AD Instance is the instance of Azure, for example public Azure or Azure China. 18 | internal static string aadInstance = ConfigurationManager.AppSettings["aad:AADInstance"]; 19 | // The Tenant is the name or Id of the Azure AD tenant in which this application is registered. 20 | internal static string tenant = ConfigurationManager.AppSettings["aad:Tenant"]; 21 | // The Client ID is used by the application to uniquely identify itself to Azure AD. 22 | internal static string clientId = ConfigurationManager.AppSettings["aad:ClientId"]; 23 | // The Authority is the sign-in URL of the tenant. 24 | internal static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant); 25 | 26 | // Azure AD Credentials 27 | internal static string username = ConfigurationManager.AppSettings["aad:Username"]; 28 | internal static string password = ConfigurationManager.AppSettings["aad:Password"]; 29 | 30 | //URL of your Azure DevOps account. 31 | internal static string azureDevOpsOrganizationUrl = ConfigurationManager.AppSettings["ado:OrganizationUrl"]; 32 | 33 | public static async Task Main(string[] args) 34 | { 35 | var result = await GetAadAccessToken(); 36 | 37 | var token = new VssAadToken(result.TokenType, result.AccessToken); 38 | var vstsCredential = new VssAadCredential(token); 39 | 40 | var connection = new VssConnection(new Uri(azureDevOpsOrganizationUrl), vstsCredential); 41 | 42 | await CreatePat(connection); 43 | } 44 | 45 | /// 46 | /// Acquires an AAD access token using a public client application (MSAL) 47 | /// 48 | /// Token acquisition result 49 | private static async Task GetAadAccessToken() 50 | { 51 | var application = PublicClientApplicationBuilder.Create(clientId) 52 | .WithAuthority(authority) 53 | .WithDefaultRedirectUri() 54 | .Build(); 55 | return await application.AcquireTokenByUsernamePassword(scopes, username, password).ExecuteAsync(); 56 | } 57 | 58 | /// 59 | /// Creates a PAT using the PAT Lifecycle Management API (https://learn.microsoft.com/en-us/rest/api/azure/devops/tokens) 60 | /// 61 | /// Azure DevOps connection 62 | private static async Task CreatePat(VssConnection connection) 63 | { 64 | var client = connection.GetClient(); 65 | 66 | var request = new PatTokenCreateRequest 67 | { 68 | DisplayName = "Generated by sample code", 69 | Scope = "vso.work vso.graph" 70 | }; 71 | 72 | var patTokenResult = await client.CreatePatAsync(request); 73 | 74 | if (patTokenResult.PatTokenError == SessionTokenError.None) 75 | { 76 | Console.WriteLine($"PAT Secret: {patTokenResult.PatToken.Token}"); 77 | } 78 | else 79 | { 80 | Console.WriteLine($"Error: {patTokenResult.PatTokenError}"); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /NonInteractivePatGenerationSample/README.md: -------------------------------------------------------------------------------- 1 | # Non-interactive PAT generation sample 2 | 3 | This sample shows how to generate a Personal Access Token (PAT) using the [Client Libraries](https://learn.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=azure-devops) and the [PAT Lifecycle Management API](https://learn.microsoft.com/en-us/rest/api/azure/devops/tokens). Requests to this API need to be authorized with an Azure Active Directory (AAD) access token. 4 | 5 | This sample uses the `PublicClientApplicationBuilder` from **Microsoft Authentication Library (MSAL)** rather than relying on the interactive pop-up dialog to get an AAD access token which is then used as a credential to authenticate requests to Azure DevOps. This is meant to be used in scenarios where you need to generate a PAT associated with an account that does not have interactive login rights. 6 | 7 | ## How to run this sample 8 | 9 | **Prerequisites** 10 | 11 | - [.NET Framework 4.7.2 SDK](https://dotnet.microsoft.com/en-us/download/dotnet-framework) 12 | - [An Application in your Azure Active Directory (Azure AD) tenant](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) 13 | - _(optional)_ [Visual Studio / Visual Studio Code](https://visualstudio.microsoft.com/downloads/) 14 | 15 | 16 | ### Step 1: Clone or download this repository 17 | 18 | From a shell or command line: 19 | ``` 20 | git clone https://github.com/microsoft/azure-devops-auth-samples.git 21 | ``` 22 | 23 | ### Step 2: Configure the sample to use your Azure AD application 24 | 25 | Update the configuration file `App.config` with the information about your AAD application, AAD credentials, and Azure DevOps organization. 26 | 27 | ### Step 3: Run the sample 28 | 29 | **From Visual Studio:** 30 | 1. Open the solution file `NonInteractivePatGenerationSample.sln`. 31 | 2. Build and run the project. 32 | 33 | -- OR -- 34 | 35 | **From the command line:** 36 | ```cmd 37 | cd NonInteractivePatGenerationSample/NonInteractivePatGenerationSample 38 | dotnet run 39 | ``` 40 | -------------------------------------------------------------------------------- /OAuthWebSample/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | *.sln.ide/ 9 | OAuthWebSample/Properties/PublishProfiles/ 10 | 11 | # Build results 12 | 13 | [Dd]ebug/ 14 | [Rr]elease/ 15 | x64/ 16 | build/ 17 | [Bb]in/ 18 | [Oo]bj/ 19 | 20 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 21 | !packages/*/build/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | *_i.c 28 | *_p.c 29 | *.ilk 30 | *.meta 31 | *.obj 32 | *.pch 33 | *.pdb 34 | *.pgc 35 | *.pgd 36 | *.rsp 37 | *.sbr 38 | *.tlb 39 | *.tli 40 | *.tlh 41 | *.tmp 42 | *.tmp_proj 43 | *.log 44 | *.vspscc 45 | *.vssscc 46 | .builds 47 | *.pidb 48 | *.log 49 | *.scc 50 | 51 | # Visual C++ cache files 52 | ipch/ 53 | *.aps 54 | *.ncb 55 | *.opensdf 56 | *.sdf 57 | *.cachefile 58 | 59 | # Visual Studio profiler 60 | *.psess 61 | *.vsp 62 | *.vspx 63 | 64 | # Guidance Automation Toolkit 65 | *.gpState 66 | 67 | # ReSharper is a .NET coding add-in 68 | _ReSharper*/ 69 | *.[Rr]e[Ss]harper 70 | 71 | # TeamCity is a build add-in 72 | _TeamCity* 73 | 74 | # DotCover is a Code Coverage Tool 75 | *.dotCover 76 | 77 | # NCrunch 78 | *.ncrunch* 79 | .*crunch*.local.xml 80 | 81 | # Installshield output folder 82 | [Ee]xpress/ 83 | 84 | # DocProject is a documentation generator add-in 85 | DocProject/buildhelp/ 86 | DocProject/Help/*.HxT 87 | DocProject/Help/*.HxC 88 | DocProject/Help/*.hhc 89 | DocProject/Help/*.hhk 90 | DocProject/Help/*.hhp 91 | DocProject/Help/Html2 92 | DocProject/Help/html 93 | 94 | # Click-Once directory 95 | publish/ 96 | 97 | # Publish Web Output 98 | *.Publish.xml 99 | 100 | # NuGet Packages Directory 101 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 102 | packages/ 103 | 104 | # Windows Azure Build Output 105 | csx 106 | *.build.csdef 107 | 108 | # Windows Store app package directory 109 | AppPackages/ 110 | 111 | # Others 112 | sql/ 113 | *.Cache 114 | ClientBin/ 115 | [Ss]tyle[Cc]op.* 116 | ~$* 117 | *~ 118 | *.dbmdl 119 | *.[Pp]ublish.xml 120 | *.pfx 121 | *.publishsettings 122 | 123 | # RIA/Silverlight projects 124 | Generated_Code/ 125 | 126 | # Backup & report files from converting an old project file to a newer 127 | # Visual Studio version. Backup files are not needed, because we have git ;-) 128 | _UpgradeReport_Files/ 129 | Backup*/ 130 | UpgradeLog*.XML 131 | UpgradeLog*.htm 132 | 133 | # SQL Server files 134 | App_Data/*.mdf 135 | App_Data/*.ldf 136 | 137 | 138 | #LightSwitch generated files 139 | GeneratedArtifacts/ 140 | _Pvt_Extensions/ 141 | ModelManifest.xml 142 | 143 | # ========================= 144 | # Windows detritus 145 | # ========================= 146 | 147 | # Windows image file caches 148 | Thumbs.db 149 | ehthumbs.db 150 | 151 | # Folder config file 152 | Desktop.ini 153 | 154 | # Recycle Bin used on file shares 155 | $RECYCLE.BIN/ 156 | 157 | # Mac desktop service store files 158 | .DS_Store 159 | 160 | 161 | # ===== 162 | # Other 163 | # ===== 164 | storage* 165 | db.lock 166 | *.pubxml 167 | 168 | .vs/ -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthWebSample", "OAuthWebSample\OAuthWebSample.csproj", "{6D848F3D-473F-45FD-8315-8FAB2F88815A}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PublishScripts", "PublishScripts", "{4D26F01F-9D2A-45AC-B807-53E66EC65B72}" 9 | ProjectSection(SolutionItems) = preProject 10 | PublishScripts\AzureWebAppPublishModule.psm1 = PublishScripts\AzureWebAppPublishModule.psm1 11 | PublishScripts\Publish-WebApplication.ps1 = PublishScripts\Publish-WebApplication.ps1 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configurations", "Configurations", "{F0D344B9-A8BE-4147-909F-3214E45204A1}" 15 | ProjectSection(SolutionItems) = preProject 16 | PublishScripts\Configurations\OAuthSample-WAWS-dev.json = PublishScripts\Configurations\OAuthSample-WAWS-dev.json 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9099F192-D034-4AB2-B628-EDB4B4C2BB81}" 20 | ProjectSection(SolutionItems) = preProject 21 | README.md = README.md 22 | EndProjectSection 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {6D848F3D-473F-45FD-8315-8FAB2F88815A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {6D848F3D-473F-45FD-8315-8FAB2F88815A}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {6D848F3D-473F-45FD-8315-8FAB2F88815A}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {6D848F3D-473F-45FD-8315-8FAB2F88815A}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {0348F5DD-66F1-4AA9-B291-FDEA2E19AC3A} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Optimization; 3 | 4 | namespace OAuthSample 5 | { 6 | public class BundleConfig 7 | { 8 | // For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862 9 | public static void RegisterBundles(BundleCollection bundles) 10 | { 11 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 12 | "~/Scripts/jquery-{version}.js")); 13 | 14 | bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( 15 | "~/Scripts/jquery.validate*")); 16 | 17 | // Use the development version of Modernizr to develop with and learn from. Then, when you're 18 | // ready for production, use the build tool at https://modernizr.com to pick only the tests you need. 19 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 20 | "~/Scripts/modernizr-*")); 21 | 22 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( 23 | "~/Scripts/bootstrap.js")); 24 | 25 | bundles.Add(new StyleBundle("~/Content/css").Include( 26 | "~/Content/bootstrap.css", 27 | "~/Content/site.css")); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | 4 | namespace OAuthSample 5 | { 6 | public class FilterConfig 7 | { 8 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 9 | { 10 | filters.Add(new HandleErrorAttribute()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace OAuthSample 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Content/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | 5 | line-height: 1.5em; 6 | } 7 | 8 | body, h1, h2, h3, h4, h5, h6 { 9 | font-family: "Segoe UI", Verdana, sans-serif; 10 | } 11 | 12 | /* Set padding to keep content from hitting the edges */ 13 | .body-content { 14 | padding-left: 15px; 15 | padding-right: 15px; 16 | } 17 | 18 | /* Set width on the form input elements since they're 100% wide by default */ 19 | input, 20 | select, 21 | textarea { 22 | max-width: 280px; 23 | } 24 | 25 | /* styles for validation helpers */ 26 | .field-validation-error { 27 | color: #b94a48; 28 | } 29 | 30 | .field-validation-valid { 31 | display: none; 32 | } 33 | 34 | input.input-validation-error { 35 | border: 1px solid #b94a48; 36 | } 37 | 38 | input[type="checkbox"].input-validation-error { 39 | border: 0 none; 40 | } 41 | 42 | .validation-summary-errors { 43 | color: #b94a48; 44 | } 45 | 46 | .validation-summary-valid { 47 | display: none; 48 | } -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Content/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/OAuthWebSample/OAuthWebSample/Content/logo.png -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | 7 | namespace OAuthSample.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public ActionResult Index() 12 | { 13 | ViewBag.ClientAppId = System.Configuration.ConfigurationManager.AppSettings["ClientAppId"]; 14 | ViewBag.CallbackUrl = System.Configuration.ConfigurationManager.AppSettings["CallbackUrl"]; 15 | ViewBag.Scope = System.Configuration.ConfigurationManager.AppSettings["Scope"]; 16 | 17 | return View(); 18 | } 19 | 20 | public ActionResult About() 21 | { 22 | ViewBag.Message = "Your application description page."; 23 | 24 | return View(); 25 | } 26 | 27 | public ActionResult Contact() 28 | { 29 | ViewBag.Message = "Your contact page."; 30 | 31 | return View(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="OAuthSample.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Optimization; 7 | using System.Web.Routing; 8 | 9 | namespace OAuthSample 10 | { 11 | public class MvcApplication : System.Web.HttpApplication 12 | { 13 | protected void Application_Start() 14 | { 15 | AreaRegistration.RegisterAllAreas(); 16 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 17 | RouteConfig.RegisterRoutes(RouteTable.Routes); 18 | BundleConfig.RegisterBundles(BundleTable.Bundles); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Models/TokenModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace OAuthSample.Models 5 | { 6 | [DataContract] 7 | public class TokenModel 8 | { 9 | [DataMember(Name = "access_token")] 10 | public String AccessToken { get; set; } 11 | 12 | [DataMember(Name = "token_type")] 13 | public String TokenType { get; set; } 14 | 15 | [DataMember(Name = "refresh_token")] 16 | public String RefreshToken { get; set; } 17 | 18 | [DataMember(Name = "expires_in")] 19 | public int ExpiresIn { get; set; } 20 | 21 | public bool IsPending { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("OAuthSample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corp.")] 12 | [assembly: AssemblyProduct("OAuthSample")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft Corp. 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4eba735e-4ab8-4817-b79c-03e850605d48")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "About"; 3 | } 4 |

@ViewBag.Title.

5 |

@ViewBag.Message

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Contact"; 3 | } 4 |

@ViewBag.Title.

5 |

@ViewBag.Message

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
-------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Azure DevOps OAuth Client Sample"; 3 | 4 | var missingMsg = "Not set - update web.config"; 5 | var clientAppIdVal = !String.IsNullOrEmpty(ViewBag.ClientAppId) ? ViewBag.ClientAppId : missingMsg; 6 | var scopeVal = !String.IsNullOrEmpty(ViewBag.Scope) ? ViewBag.Scope : missingMsg; 7 | var callbackUrlVal = !String.IsNullOrEmpty(ViewBag.CallbackUrl) ? ViewBag.CallbackUrl : missingMsg; 8 | } 9 | 10 |
11 |

Azure DevOps OAuth Client Sample

12 |

This app shows how to authorize a user to authorize an app and then to request an access token to access Azure DevOps on their behalf.

13 |

Authorize »

14 |
15 | 16 |
17 |
18 |

Setup

19 |

20 | Before you start, make sure to: 21 |

    22 |
  1. Register a client app with Azure DevOps
  2. 23 |
  3. Update the web.config of this web app and set the App ID, Scope, App Secret, and Callback URL set in the registered app. The callback URL should be https://site/oauth/callback 24 |
      25 |
    • App ID: @clientAppIdVal
    • 26 |
    • Scope: @scopeVal
    • 27 |
    • Callback URL: @callbackUrlVal
    • 28 |
    29 |
  4. 30 |
31 |

32 |
33 |
34 |

Learn about auth in Azure DevOps

35 |

Azure DevOps supports authorization via OAuth 2.0. Learn more about how you can develop apps that securely access your user's Azure DevOps projects and perform tasks on their behalf.

36 |

Learn more »

37 |
38 |
39 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/OAuth/TokenView.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Details"; 3 | } 4 | 5 | 18 | 19 |

Token details

20 | 21 | @if (!String.IsNullOrEmpty(ViewBag.Error)) 22 | { 23 |

@ViewBag.Error

24 | } 25 | else 26 | { 27 | 48 | 49 |
50 | 51 |

Access token:

52 | 53 | 54 |

Refresh token:

55 | 56 | 57 |

Expiration (seconds):

58 | 59 | 60 |

61 | 62 |
63 | } 64 | 65 |

View profile

66 |

Return to start

67 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = null; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | Error 10 | 11 | 12 |
13 |

Error.

14 |

An error occurred while processing your request.

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | @Styles.Render("~/Content/css") 8 | @Scripts.Render("~/bundles/modernizr") 9 | 10 | 11 | 23 |
25 | @RenderBody() 26 |
27 |
28 | 29 | @Scripts.Render("~/bundles/jquery") 30 | @Scripts.Render("~/bundles/bootstrap") 31 | @RenderSection("scripts", required: false) 32 | 33 | 34 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/OAuthWebSample/OAuthWebSample/favicon.ico -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/OAuthWebSample/OAuthWebSample/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /OAuthWebSample/OAuthWebSample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /OAuthWebSample/PublishScripts/Configurations/OAuthSample-WAWS-dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "environmentSettings": { 3 | "webSite": { 4 | "name": "OAuthSample", 5 | "location": "East US" 6 | }, 7 | "databases": [ 8 | { 9 | "connectionStringName": "", 10 | "databaseName": "", 11 | "serverName": "", 12 | "user": "", 13 | "password": "", 14 | "edition": "", 15 | "size": "", 16 | "collation": "" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /OAuthWebSample/README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET web app (Azure DevOps OAuth sample) 2 | 3 | This sample shows how to prompt a user to authorize a cloud service that can call APIs on Azure DevOps on behalf of the user. 4 | 5 | To learn more about OAuth in Azure DevOps, see [Authorize access to Azure DevOps with OAuth 2.0](https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=vsts) 6 | 7 | 8 | ## How to setup 9 | 10 | > These instructions assume you will be deploying this sample app to an Azure web app. To learn more and to get started, visit [Get started with Azure Web Apps and ASP.NET](https://docs.microsoft.com/azure/app-service/app-service-web-get-started-dotnet-framework). 11 | 12 | 1. Register an OAuth client app in Azure DevOps (https://app.vsaex.visualstudio.com/app/register) 13 | * The callback URL should be https://yoursite.azurewebsites.net/oauth/callback, where `yoursite` is the name of your Azure web app 14 | 15 | 2. Clone this repository and open the solution `OAuthWebSample\OAuthWebSample.sln` in Visual Studio 2015 or later 16 | 17 | 3. Update the following settings in web.config to match the values in the app you just registered: 18 | * `ClientAppID` 19 | * `ClientAppSecret` (use the "Client Secret" shown on the Azure DevOps Application Settings page, not the App Secret) 20 | * `Scope` (space separated) 21 | * `CallbackUrl` 22 | 23 | 4. Build the solution (this will trigger a NuGet package restore, which will pull in all dependencies of the project) 24 | 25 | 5. Publish the app to Azure 26 | 27 | ### Run the sample 28 | 29 | 1. Navigate to your app (https://yoursite.azurewebsites.net) 30 | 31 | 2. Confirm your App ID, scope, and callback URL are displayed properly 32 | ![app](appstart.png) 33 | 34 | 3. Click **Authorize** 35 | 36 | 4. Sign in to Azure DevOps (if prompted) 37 | 38 | 5. Review and accept the authorization request 39 | 40 | If everything is setup properly, Azure DevOps will issue an access token and refresh token and both values will be displayed. **You should keep these values secret**. Also a new authorization will appear in [your profile page](https://app.vssps.visualstudio.com/Profile/View). 41 | 42 | 43 | With the access token you can invoke [Azure DevOps REST APIs](https://docs.microsoft.com/en-us/rest/api/vsts/?view=vsts-rest-4.1) by providing the access token in the Authorization header. 44 | 45 | ``` 46 | Authorization: Bearer {access token} 47 | ``` 48 | -------------------------------------------------------------------------------- /OAuthWebSample/appstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/OAuthWebSample/appstart.png -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/.gitignore: -------------------------------------------------------------------------------- 1 | flask_session 2 | __pycache__ 3 | .vscode -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/AppCreationScripts/Apps.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "Title": "Integrating Azure AD into a Python web application", 4 | "Level": 400, 5 | "Client": "Python, MSAL.Python" 6 | }, 7 | "AppRegistrations": [ 8 | { 9 | "x-ms-id": "PythonWebApp", 10 | "x-ms-name": "ms-identity-python-webapp", 11 | "x-ms-version": "2.0", 12 | "replyUrlsWithType": [ 13 | { 14 | "url": "http://localhost:5000/getAToken", 15 | "type": "Web" 16 | } 17 | ], 18 | "passwordCredentials": [ 19 | { 20 | "value": "{auto}" 21 | } 22 | ], 23 | "x-ms-passwordCredentials": "Auto", 24 | "oauth2AllowImplicitFlow": false, 25 | "oauth2AllowIdTokenImplicitFlow": false, 26 | "requiredResourceAccess": [ 27 | { 28 | "x-ms-resourceAppName": "Microsoft Graph", 29 | "resourceAppId": "00000003-0000-0000-c000-000000000000", 30 | "resourceAccess": [ 31 | { 32 | "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d", 33 | "type": "Scope", 34 | "x-ms-name": "user.read" 35 | }, 36 | { 37 | "id": "b340eb25-3456-403f-be2f-af7a0d370277", 38 | "type": "Scope", 39 | "x-ms-name": "User.ReadBasic.All" 40 | } 41 | ] 42 | } 43 | ], 44 | "codeConfigurations": [ 45 | { 46 | "settingFile": "/app_config.py", 47 | "replaceTokens": { 48 | "appId": "Enter_the_Application_Id_here", 49 | "tenantId": "common", 50 | "clientSecret": "Enter_the_Client_Secret_Here", 51 | "authorityEndpointHost": "https://login.microsoftonline.com/", 52 | "msgraphEndpointHost": "https://graph.microsoft.com/" 53 | } 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/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 'pythonwebapp' (python-webapp) if needed" 53 | Get-AzureADApplication -Filter "DisplayName eq 'python-webapp'" | ForEach-Object {Remove-AzureADApplication -ObjectId $_.ObjectId } 54 | $apps = Get-AzureADApplication -Filter "DisplayName eq 'python-webapp'" 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 python-webapp.." 64 | } 65 | # also remove service principals of this app 66 | Get-AzureADServicePrincipal -filter "DisplayName eq 'python-webapp'" | ForEach-Object {Remove-AzureADServicePrincipal -ObjectId $_.Id -Confirm:$false} 67 | 68 | } 69 | 70 | Cleanup -Credential $Credential -tenantId $TenantId -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/AppCreationScripts/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sample": { 3 | "RepositoryUrl": "https://github.com/Azure-Samples/ms-identity-python-webapp", 4 | "Title": "Integrating Microsoft Identity Platform with a Python web application", 5 | "Level": 300, 6 | "Client": "Python Web Application", 7 | "Service": "Microsoft Graph", 8 | "Endpoint": "Microsoft identity platform (formerly Azure AD v2.0)" 9 | }, 10 | 11 | /* 12 | This section describes the Azure AD Applications to configure, and their dependencies 13 | */ 14 | "AADApps": [ 15 | { 16 | "Id": "pythonwebapp", 17 | "Name": "python-webapp", 18 | "Kind": "WebApp", /* SinglePageApplication, WebApp, Mobile, UWP, Desktop, Daemon, WebApi, Browserless */ 19 | "Audience": "AzureADandPersonalMicrosoftAccount", /* AzureADMyOrg, AzureADMultipleOrgs, AzureADandPersonalMicrosoftAccount, PersonalMicrosoftAccount */ 20 | "PasswordCredentials": "Auto", 21 | "RequiredResourcesAccess": [ 22 | { 23 | "Resource": "Microsoft Graph", 24 | "DelegatedPermissions": [ 25 | "User.ReadBasic.All" 26 | ] 27 | } 28 | ], 29 | "ReplyUrls": "http://localhost:5000/getAToken" 30 | } 31 | ], 32 | 33 | /* 34 | This section describes how to update the code in configuration files from the apps coordinates, once the apps 35 | are created in Azure AD. 36 | Each section describes a configuration file, for one of the apps, it's type (XML, JSon, plain text), its location 37 | with respect to the root of the sample, and the mappping (which string in the config file is mapped to which value 38 | */ 39 | "CodeConfiguration": [ 40 | { 41 | "App": "pythonwebapp", 42 | "SettingKind": "Replace", 43 | "SettingFile": "\\..\\app_config.py", 44 | "Mappings": [ 45 | { 46 | "key": "Enter_the_Tenant_Name_Here", 47 | "value": "$tenantName" 48 | }, 49 | { 50 | "key": "Enter_the_Client_Secret_Here", 51 | "value": ".AppKey" 52 | }, 53 | { 54 | "key": "Enter_the_Application_Id_here", 55 | "value": ".AppId" 56 | } 57 | ] 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/ReadmeFiles/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/azure-devops-auth-samples/9097287405fde466a745dfb94c2dcaf0860a79e9/PersonalAccessTokenAPIAppSample/ReadmeFiles/topology.png -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/app_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # To configure this application, fill in your application (client) ID, client secret, 4 | # AAD tenant ID, and Azure DevOps collection name in the placeholders below. 5 | 6 | CLIENT_ID = "Enter_the_Application_Id_here" 7 | # Application (client) ID of app registration 8 | 9 | CLIENT_SECRET = "Enter_the_Client_Secret_here" 10 | # In a production app, we recommend you use a more secure method of storing your secret, 11 | # like Azure Key Vault. Or, use an environment variable as described in Flask's documentation: 12 | # https://flask.palletsprojects.com/en/1.1.x/config/#configuring-from-environment-variables 13 | # CLIENT_SECRET = os.getenv("CLIENT_SECRET") 14 | # if not CLIENT_SECRET: 15 | # raise ValueError("Need to define CLIENT_SECRET environment variable") 16 | 17 | AUTHORITY = "https://login.microsoftonline.com/Enter_the_Tenant_ID_Here" # For multi-tenant app 18 | # AUTHORITY = "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here" 19 | 20 | REDIRECT_PATH = "/getAToken" # Used for forming an absolute URL to your redirect URI. 21 | # The absolute URL must match the redirect URI you set 22 | # in the app's registration in the Azure portal. 23 | 24 | 25 | ENDPOINT = 'https://vssps.dev.azure.com/Enter_the_Collection_Name_Here/_apis/Tokens/Pats?api-version=6.1-preview' 26 | # fill in the url to the user's ADO collection name here 27 | 28 | SCOPE = ["499b84ac-1321-427f-aa17-267ca6975798/.default"] 29 | # Means "All scopes for the Azure DevOps API resource" 30 | 31 | SESSION_TYPE = "filesystem" 32 | # Specifies the token cache should be stored in server-side session 33 | -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask>=1,<2 2 | werkzeug>=1,<2 3 | flask-session~=0.3.2 4 | requests>=2,<3 5 | msal>=0.6.1,<2 6 | 7 | -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/templates/auth_error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if config.get("B2C_RESET_PASSWORD_AUTHORITY") and "AADB2C90118" in result.get("error_description") %} 7 | 8 | 9 | {% endif %} 10 | 11 | 12 |

Login Failure

13 |
14 |
{{ result.get("error") }}
15 |
{{ result.get("error_description") }}
16 |
17 |
18 | Homepage 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/templates/display.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Back 8 |

PAT Lifecycle API Call Result

9 |
{{ result |tojson(indent=4) }}
10 | 11 | 12 | -------------------------------------------------------------------------------- /PersonalAccessTokenAPIAppSample/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Microsoft Identity Python Web App

8 | 9 |
  • Sign In
  • 10 | 11 | {% if config.get("B2C_RESET_PASSWORD_AUTHORITY") %} 12 |
  • Reset Password
  • 13 | {% endif %} 14 | 15 |
    16 |
    Powered by MSAL Python {{ version }}
    17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auth samples for Azure DevOps Services 2 | 3 | ![status](https://dev.azure.com/mseng/_apis/public/build/definitions/b924d696-3eae-4116-8443-9a18392d8544/5326/badge) 4 | 5 | Samples that show how to authenticate with Azure DevOps and Azure DevOps Server. 6 | 7 | Learn more about [integrating with Azure DevOps](https://docs.microsoft.com/en-us/azure/devops/extend/overview?view=vsts) and [specific authentication guidance](https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/authentication-guidance?view=vsts) 8 | 9 | ## Samples 10 | 11 | * [Managed client sample (using Azure Active Directory Library)](./ManagedClientConsoleAppSample/README.md) 12 | * [Device profile sample (.NET Core)](./DeviceProfileSample/README.md) 13 | * [ASP.NET Web app OAuth sample](./OAuthWebSample/README.md) 14 | * [Client library sample (using VSSConnection)](./ClientLibraryConsoleAppSample/README.md) 15 | * [Javascript web app sample (using Microsoft Authentication Library for JavaScript)](./JavascriptWebAppSample/README.md) 16 | * [Dual Support (Azure DevOps/TFS) Client Sample (using Azure Active Directory Library and Windows Authentication)](./DualSupportClientSample/README.md) 17 | * [Non-interactive PAT Generation Sample (using Azure Active Directory Library with a Username Password credential)](./NonInteractivePatGenerationSample/README.md) 18 | * [PAT lifecycle management API sample (using Microsoft Authentication Library with authentication code)](./PersonalAccessTokenAPIAppSample/README.md) 19 | * [Azure AD Service Principals and Managed Identities in Azure DevOps](/ServicePrincipalsSamples/) -------------------------------------------------------------------------------- /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 [many more](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](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.** Instead, please report them to the Microsoft Security Response Center at [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://technet.microsoft.com/en-us/security/dn606155). 12 | 13 | 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). 14 | 15 | 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: 16 | 17 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 18 | * Full paths of source file(s) related to the manifestation of the issue 19 | * The location of the affected source code (tag/branch/commit or direct URL) 20 | * Any special configuration required to reproduce the issue 21 | * Step-by-step instructions to reproduce the issue 22 | * Proof-of-concept or exploit code (if possible) 23 | * Impact of the issue, including how an attacker might exploit the issue 24 | 25 | This information will help us triage your report more quickly. 26 | 27 | ## Preferred Languages 28 | 29 | We prefer all communications to be in English. 30 | 31 | ## Policy 32 | 33 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 34 | 35 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/0-SimpleConsoleApp-AppRegistration/0-SimpleConsoleApp-AppRegistration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/0-SimpleConsoleApp-AppRegistration/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography.X509Certificates; 2 | using Azure.Core; 3 | using Azure.Identity; 4 | using Microsoft.TeamFoundation.WorkItemTracking.WebApi; 5 | using Microsoft.VisualStudio.Services.Client; 6 | using Microsoft.VisualStudio.Services.WebApi; 7 | using Microsoft.VisualStudio.Services.WebApi.Patch; 8 | using Microsoft.VisualStudio.Services.WebApi.Patch.Json; 9 | 10 | 11 | /// PARAMETERS 12 | const string AdoBaseUrl = "https://dev.azure.com"; 13 | const string AdoOrgName = "Your organization name"; 14 | 15 | const string AadTenantId = "Your Azure AD tenant id"; 16 | const string AadClientId = ""; // Client ID for your App Registration / Service Principal 17 | // Set one of either clientSecret or certificateThumbprint 18 | const string AadClientSecret = ""; // Client Secret for your App Registration / Service Principal 19 | const string AadCertificateThumbprint = ""; // Thumbprint for your client certificate 20 | 21 | 22 | /// CODE 23 | TokenCredential credential; 24 | 25 | if (!string.IsNullOrEmpty(AadClientSecret)) 26 | { 27 | credential = new ClientSecretCredential(AadTenantId, AadClientId, AadClientSecret); 28 | } 29 | else 30 | { 31 | using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); // Replace with appropriate Store Name / Location if necessary 32 | store.Open(OpenFlags.ReadOnly); 33 | var certificate = store.Certificates.Cast().FirstOrDefault(cert => cert.Thumbprint == AadCertificateThumbprint); 34 | 35 | credential = new ClientCertificateCredential(AadTenantId, AadClientId, certificate); 36 | } 37 | 38 | // Whenever possible, credential instance should be reused for the lifetime of the process. 39 | // An internal token cache is used which reduces the number of outgoing calls to Azure AD to get tokens. 40 | // Call GetTokenAsync whenever you are making a request. Token caching and refresh logic is handled by the credential object. 41 | var vssAadCredentials = new VssAzureIdentityCredential(credential); 42 | 43 | var orgUrl = new Uri(new Uri(AdoBaseUrl), AdoOrgName); 44 | var connection = new VssConnection(orgUrl, vssAadCredentials); 45 | 46 | 47 | var client = connection.GetClient(); 48 | 49 | Console.Write("Work Item Command? [get or create] "); 50 | var command = Console.ReadLine().Trim().ToLowerInvariant(); 51 | Console.WriteLine(); 52 | Console.Write("Azure DevOps Project Name? "); 53 | var project = Console.ReadLine().Trim(); 54 | Console.WriteLine(); 55 | 56 | if (command == "get") 57 | { 58 | Console.Write("Work Item ID? "); 59 | var workItemId = int.Parse(Console.ReadLine().Trim()); 60 | Console.WriteLine(); 61 | var workItem = await client.GetWorkItemAsync(project, workItemId); 62 | Console.WriteLine(workItem.Fields["System.Title"]); 63 | } 64 | else if (command == "create") 65 | { 66 | Console.Write("Work Item Title? "); 67 | var title = Console.ReadLine().Trim(); 68 | Console.WriteLine(); 69 | var patchDocument = new JsonPatchDocument 70 | { 71 | new JsonPatchOperation() 72 | { 73 | Operation = Operation.Add, 74 | Path = "/fields/System.Title", 75 | Value = title 76 | } 77 | }; 78 | 79 | try 80 | { 81 | 82 | var result = await client.CreateWorkItemAsync(patchDocument, project, "bug"); 83 | Console.WriteLine($"Work item created: Id = {result.Id}"); 84 | } 85 | catch (Exception ex) 86 | { 87 | Console.WriteLine(ex.Message); 88 | return -1; 89 | } 90 | } 91 | 92 | 93 | return 0; 94 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/0-SimpleConsoleApp-AppRegistration/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "0-ConsoleApp-AppRegistration": { 4 | "commandName": "Project", 5 | "commandLineArgs": "" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/0-SimpleConsoleApp-AppRegistration/README.md: -------------------------------------------------------------------------------- 1 | # Simple .NET Core console application using an Azure AD Application to create/get work items 2 | 3 | This sample shows how to get an Azure AD access token for an Application Service Principal (using a secret or a certificate) using [Azure Identity client library for .NET](https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme?view=azure-dotnet) and authenticate to Azure DevOps to create or get a work item. 4 | 5 | ## How to run this sample 6 | 7 | **Prerequisites** 8 | 9 | - [.NET Core SDK](https://dotnet.microsoft.com/en-us/download) - 6.0 or higher 10 | - [Azure DevOps .NET client libraries](https://learn.microsoft.com/en-us/azure/devops/integrate/concepts/dotnet-client-libraries?view=azure-devops) - 19.219.0-preview or higher 11 | - [Visual Studio / Visual Studio Code](https://aka.ms/vsdownload) 12 | 13 | ### Step 1: Clone or download this repository 14 | 15 | From a shell or command line: 16 | 17 | ```ps 18 | git clone https://github.com/microsoft/azure-devops-auth-samples.git 19 | ``` 20 | 21 | ### Step 2: Create an Azure AD application 22 | 23 | In the tenant to which your Azure DevOps organization is connected, [create an Azure AD Application](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app). 24 | 25 | ### Step 3: Add the Azure AD application to your Azure DevOps Organization 26 | 27 | Once the application is created, [add it to your Azure DevOps organization](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity#step-by-step-configuration). 28 | 29 | ### Step 4: Configure the sample to use your Azure AD application 30 | 31 | Update parameters in the file `Program.cs` with the information about your Azure AD application and Azure DevOps organization. 32 | 33 | ```cs 34 | /// PARAMETERS 35 | const string orgName = "YOUR ORG NAME"; 36 | 37 | const string tenantId = "YOUR TENANT ID"; 38 | const string clientId = ""; // Client ID for your App Registration / Service Principal 39 | // Set one of either clientSecret or certificateThumbprint 40 | const string clientSecret = ""; // Client Secret for your App Registration / Service Principal 41 | const string certificateThumbprint = ""; // Thumbprint for your client certificate 42 | ``` 43 | 44 | ### Step 5: Run the sample 45 | 46 | From the console: 47 | 48 | ```cmd 49 | cd 0-SimpleConsoleApp-AppRegistration 50 | dotnet run 51 | ``` 52 | 53 | # References 54 | 55 | - [Azure.Identity - ClientCertificateCredentials](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.clientcertificatecredential?view=azure-dotnet) 56 | - [Azure.Identity - ClientSecretCredentials](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.clientsecretcredential?view=azure-dotnet) -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/1-ConsoleApp-AppRegistration.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | ServicePrincipalsSamples 7 | 8 | 9 | 10 | TRACE 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/Aad/AadAccessTokenHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Services.Client; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace ServicePrincipalsSamples.Aad 8 | { 9 | /// 10 | /// Adds an Azure AD access token for Azure DevOps as authentication mechanism for every request 11 | /// 12 | public class AadAccessTokenHandler : DelegatingHandler 13 | { 14 | private readonly AadClient _aadClient; 15 | 16 | public AadAccessTokenHandler(AadClient aadClient) 17 | { 18 | _aadClient = aadClient; 19 | } 20 | 21 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 22 | { 23 | var result = await _aadClient.GetAadAccessToken(VssAadSettings.DefaultScopes); 24 | request.Headers.Authorization = new AuthenticationHeaderValue(result.TokenType, result.AccessToken); 25 | return await base.SendAsync(request, cancellationToken); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/Aad/AadClient.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using Microsoft.Identity.Web; 3 | using ServicePrincipalsSamples.Settings; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace ServicePrincipalsSamples.Aad 8 | { 9 | /// 10 | /// Azure AD client for a App Registration Service Principal 11 | /// 12 | public class AadClient 13 | { 14 | private const string ClientSecretPlaceholderValue = "[Enter here a client secret for your application]"; 15 | 16 | private IConfidentialClientApplication app; 17 | 18 | public AadClient(AppConfiguration config) 19 | { 20 | Initialize(config); 21 | } 22 | 23 | private void Initialize(AppConfiguration config) 24 | { 25 | if (IsAppUsingClientSecret(config)) 26 | { 27 | app = ConfidentialClientApplicationBuilder.Create(config.Aad.ClientId) 28 | .WithClientSecret(config.Aad.ClientSecret) 29 | .WithAuthority(config.Aad.Authority) 30 | .Build(); 31 | } 32 | else 33 | { 34 | ICertificateLoader certificateLoader = new DefaultCertificateLoader(); 35 | certificateLoader.LoadIfNeeded(config.Aad.Certificate); 36 | 37 | app = ConfidentialClientApplicationBuilder.Create(config.Aad.ClientId) 38 | .WithCertificate(config.Aad.Certificate.Certificate) 39 | .WithAuthority(config.Aad.Authority) 40 | .Build(); 41 | } 42 | 43 | app.AddInMemoryTokenCache(); 44 | } 45 | 46 | private static bool IsAppUsingClientSecret(AppConfiguration config) 47 | { 48 | if (!string.IsNullOrWhiteSpace(config.Aad.ClientSecret) && config.Aad.ClientSecret != ClientSecretPlaceholderValue) 49 | { 50 | return true; 51 | } 52 | else if (config.Aad.Certificate != null) 53 | { 54 | return false; 55 | } 56 | else 57 | { 58 | throw new ArgumentException("You must choose between using secret or certificate. Please update appsettings.json file."); 59 | } 60 | } 61 | 62 | /// 63 | /// Returns an Azure AD access token (client credentials). It uses an in-memory cache and it also regenerates the access token if it is expired. 64 | /// 65 | /// Valid Azure AD access token 66 | public async Task GetAadAccessToken(string[] scopes) 67 | { 68 | // Client credentials flow uses the cache by default 69 | var result = await app.AcquireTokenForClient(scopes).ExecuteAsync(); 70 | Console.ForegroundColor = ConsoleColor.Green; 71 | Console.WriteLine($"Token acquired for the Service Principal (source: '{result.AuthenticationResultMetadata.TokenSource}')\n"); 72 | Console.ResetColor(); 73 | 74 | return result; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/AdoClientBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Services.Identity; 2 | using Microsoft.VisualStudio.Services.WebApi; 3 | 4 | namespace ServicePrincipalsSamples.AdoClient 5 | { 6 | /// 7 | /// Azure DevOps REST client using client libs 8 | /// 9 | /// Azure DevOps HTTP Client 10 | public abstract class AdoClientBase where T : VssHttpClientBase 11 | { 12 | protected readonly AdoConnection adoConnection; 13 | private T client; 14 | 15 | protected AdoClientBase(AdoConnection adoConnection) 16 | { 17 | this.adoConnection = adoConnection; 18 | } 19 | 20 | public Identity GetAuthorizedIdentity() 21 | { 22 | return adoConnection.VssConnection.AuthorizedIdentity; 23 | } 24 | 25 | protected T GetClient() 26 | { 27 | client ??= adoConnection.VssConnection.GetClient(); 28 | return client; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/AdoConnection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Services.Client; 2 | using Microsoft.VisualStudio.Services.Common; 3 | using Microsoft.VisualStudio.Services.WebApi; 4 | using ServicePrincipalsSamples.Aad; 5 | using ServicePrincipalsSamples.Settings; 6 | using System; 7 | using System.Net.Http; 8 | 9 | namespace ServicePrincipalsSamples.AdoClient 10 | { 11 | /// 12 | /// Wraps the Azure DevOps connection with the supported authentication mechanisms on this application 13 | /// 14 | public class AdoConnection : IDisposable 15 | { 16 | /// 17 | /// IMPORTANT: The VssConnection instance should be a singleton in the application. 18 | /// 19 | public VssConnection VssConnection { get; private set; } 20 | 21 | private bool disposedValue; 22 | 23 | public AdoConnection(AppConfiguration config, AadClient aadClient) 24 | { 25 | VssConnection = CreateVssConnection(config, aadClient); 26 | } 27 | 28 | private static VssConnection CreateVssConnection(AppConfiguration config, AadClient aadClient) 29 | { 30 | VssCredentials credentials; 31 | 32 | if (config.Ado.AdoAuthenticationMode == AdoAuthenticationMode.AdoPat) 33 | { 34 | credentials = CreateVssConnectionWithPAT(config); 35 | } 36 | else if (config.Ado.AdoAuthenticationMode == AdoAuthenticationMode.AadServicePrincipal) 37 | { 38 | credentials = CreateVssConnectionWithAadAccessToken(aadClient); 39 | } else 40 | { 41 | throw new InvalidOperationException($"Unsupported authentication mode: {config.Ado.AdoAuthenticationMode}"); 42 | } 43 | 44 | var settings = VssClientHttpRequestSettings.Default.Clone(); 45 | // Custom UserAgent with format: " ") 46 | // E.g.: "VSServices/16.170.30907.1 (NetStandard; Microsoft Windows 10.0.22621) Identity.ServicePrincipalsSamples/1.0 (1-ConsoleApp-AppRegistration)" 47 | settings.UserAgent = AppConfiguration.AppUserAgent; 48 | 49 | var innerHandlers = new VssHttpMessageHandler(credentials, settings); 50 | 51 | var delegatingHandlers = new DelegatingHandler[] { new AdoRequestHandler() }; 52 | 53 | return new VssConnection(config.Ado.OrganizationUrl, innerHandlers, delegatingHandlers); 54 | } 55 | 56 | /// 57 | /// Creates credentials with an Azure AD Service Prinicpal acces token as authentication mechanism. 58 | /// The token regeneration once it is expired is handled by the AadClient. 59 | /// 60 | /// Azure AD client 61 | /// 62 | private static VssCredentials CreateVssConnectionWithAadAccessToken(AadClient aadClient) 63 | { 64 | var vssAadToken = new VssAadToken((scopes) => aadClient.GetAadAccessToken(scopes).SyncResultConfigured()); 65 | return new VssAadCredential(vssAadToken); 66 | } 67 | 68 | /// 69 | /// Creates credentials with an Azure DevOps PAT as authentication mechanism 70 | /// 71 | /// app configuration 72 | /// 73 | private static VssCredentials CreateVssConnectionWithPAT(AppConfiguration config) 74 | { 75 | return new VssBasicCredential(string.Empty, config.Ado.Pat); 76 | } 77 | 78 | public void Dispose() 79 | { 80 | Dispose(disposing: true); 81 | GC.SuppressFinalize(this); 82 | } 83 | 84 | protected virtual void Dispose(bool disposing) 85 | { 86 | if (!disposedValue) 87 | { 88 | if (disposing) 89 | { 90 | VssConnection.Dispose(); 91 | } 92 | 93 | disposedValue = true; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/AdoRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace ServicePrincipalsSamples 7 | { 8 | public class AdoRequestHandler : DelegatingHandler 9 | { 10 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 11 | { 12 | Console.WriteLine($"Request: {request.Method} {request.RequestUri}\n"); 13 | return await base.SendAsync(request, cancellationToken); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/AdoRestClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using Microsoft.AspNetCore.WebUtilities; 6 | using System.Text.Json.Nodes; 7 | using ServicePrincipalsSamples.Settings; 8 | 9 | namespace ServicePrincipalsSamples.AdoClient 10 | { 11 | public interface IAdoRestClient 12 | { 13 | Task GetWorkItem(int workItemId); 14 | 15 | Task AddAadUserToGroup(string adoGroupSD, string userObjectId); 16 | } 17 | 18 | /// 19 | /// Azure DevOps simple REST client not using client libs 20 | /// 21 | public class AdoRestClient : IAdoRestClient 22 | { 23 | internal const string SpsUrlFragment = "vssps"; 24 | 25 | private readonly AppConfiguration config; 26 | private readonly HttpClient httpClient; 27 | 28 | public AdoRestClient(AppConfiguration config, HttpClient httpClient) 29 | { 30 | this.config = config; 31 | this.httpClient = httpClient; 32 | } 33 | 34 | public async Task GetWorkItem(int workItemId) 35 | { 36 | var path = $"_apis/wit/workItems/{workItemId}"; 37 | var url = CreateBaseAdoOrgUrl(path, version: "7.1-preview.3"); 38 | 39 | var responseString = await httpClient.GetStringAsync(url); 40 | 41 | return JsonNode.Parse(responseString); 42 | } 43 | 44 | public async Task AddAadUserToGroup(string adoGroupSD, string userObjectId) 45 | { 46 | var path = "_apis/Graph/users"; 47 | var queryParams = new Dictionary 48 | { 49 | { "groupDescriptors", adoGroupSD } 50 | }; 51 | var url = CreateAdoOrgUrlForService(SpsUrlFragment, path, queryParams); 52 | 53 | var user = new 54 | { 55 | originId = userObjectId 56 | }; 57 | 58 | var response = await httpClient.PostAsJsonAsync(url, user); 59 | var responseString = await response.Content.ReadAsStringAsync(); 60 | 61 | return JsonNode.Parse(responseString); 62 | } 63 | 64 | #region Private methods 65 | 66 | private string CreateBaseAdoOrgUrl(string path, Dictionary queryParams = null, string version = null) 67 | { 68 | return CreateAdoOrgUrl(config.Ado.OrganizationUrl, path, queryParams, version); 69 | } 70 | 71 | private string CreateAdoOrgUrlForService(string serviceUrlFragment, string path, Dictionary queryParams = null, string version = null) 72 | { 73 | return CreateAdoOrgUrl(GetOrgUrlWithFragment(serviceUrlFragment), path, queryParams, version); 74 | } 75 | 76 | private string CreateAdoOrgUrl(Uri baseUrl, string path, Dictionary queryParams, string version = null) 77 | { 78 | var uriBuilder = new UriBuilder(baseUrl) 79 | { 80 | Path = $"{config.Ado.Organization}/{path}" 81 | }; 82 | 83 | queryParams ??= new Dictionary(); 84 | queryParams.Add("api-version", version ?? AdoConfiguration.DefaultApiVersion); 85 | 86 | return QueryHelpers.AddQueryString(uriBuilder.ToString(), queryParams); 87 | } 88 | 89 | /// 90 | /// Returns the URL with the service (if specified). Example: https://vssps.dev.azure.com/{AdoOrgName} 91 | /// 92 | /// If not provided, it default to the ADO base URL 93 | public Uri GetOrgUrlWithFragment(string serviceUrlFragment) 94 | { 95 | var baseOrgUrl = config.Ado.OrganizationUrl; 96 | var uriBuilder = new UriBuilder(baseOrgUrl); 97 | uriBuilder.Host = $"{serviceUrlFragment}.{uriBuilder.Host}"; 98 | return uriBuilder.Uri; 99 | } 100 | 101 | #endregion 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/GraphClient.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Services.Common; 2 | using Microsoft.VisualStudio.Services.Graph.Client; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace ServicePrincipalsSamples.AdoClient 8 | { 9 | public class GraphClient : AdoClientBase 10 | { 11 | public GraphClient(AdoConnection adoConnection) : base(adoConnection) { } 12 | 13 | public async Task AddAadUserToGroup(string adoGroupSD, string userObjectId) 14 | { 15 | var groupDescriptors = new[] { SubjectDescriptor.FromString(adoGroupSD) }; 16 | 17 | var userContext = new GraphUserOriginIdCreationContext() 18 | { 19 | OriginId = userObjectId, 20 | }; 21 | 22 | return await GetClient().CreateUserAsync(userContext, groupDescriptors); 23 | } 24 | 25 | /// 26 | /// Returns Azure AD users in the organization 27 | /// 28 | /// Number of pages to return (0 means all pages) 29 | /// Azure AD users in the organization 30 | public async Task> ListAadUsers(int pages = 0) 31 | { 32 | int pageCounter = 0; 33 | string continuationToken = null; 34 | IList users = new List(); 35 | 36 | do 37 | { 38 | var page = await GetClient().ListUsersAsync(subjectTypes: new[] { "aad" }, continuationToken); 39 | users.AddRange(page.GraphUsers); 40 | continuationToken = page.ContinuationToken?.First(); 41 | pageCounter++; 42 | } while ((pages == 0 || pageCounter < pages) && continuationToken != null); 43 | 44 | return users; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/MemberEntitlementsClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.VisualStudio.Services.MemberEntitlementManagement.WebApi; 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.VisualStudio.Services.Common; 6 | 7 | namespace ServicePrincipalsSamples.AdoClient 8 | { 9 | public class MemberEntitlementsClient : AdoClientBase 10 | { 11 | public MemberEntitlementsClient(AdoConnection adoConnection) : base(adoConnection) { } 12 | 13 | /// 14 | /// Returns users in the organization 15 | /// 16 | /// Number of pages to return (0 means all pages) 17 | /// users in the organization 18 | public async Task> SearchUserEntitlements(int pages = 0) 19 | { 20 | int pageCounter = 0; 21 | string continuationToken = null; 22 | IList users = new List(); 23 | 24 | do 25 | { 26 | var page = await GetClient().SearchUserEntitlementsAsync(continuationToken, orderBy: "name"); 27 | users.AddRange(page.Members); 28 | continuationToken = page.ContinuationToken; 29 | pageCounter++; 30 | } while ((pages == 0 || pageCounter < pages) && continuationToken != null); 31 | 32 | return users; 33 | } 34 | 35 | public async Task GetServicePrincipalEntitlement(Guid servicePrinicipalId) 36 | { 37 | return await GetClient().GetServicePrincipalEntitlementAsync(servicePrinicipalId); 38 | } 39 | 40 | public async Task GetServicePrincipalEntitlementMe() 41 | { 42 | var servicePrincipalId = adoConnection.VssConnection.AuthorizedIdentity.Id; 43 | return await GetServicePrincipalEntitlement(servicePrincipalId); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/ProjectsClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Collections.Generic; 4 | using Microsoft.TeamFoundation.Core.WebApi; 5 | 6 | namespace ServicePrincipalsSamples.AdoClient 7 | { 8 | public class ProjectsClient : AdoClientBase 9 | { 10 | public ProjectsClient(AdoConnection adoConnection) : base(adoConnection) { } 11 | 12 | public async Task> ListProjects() 13 | { 14 | var projects = await GetClient().GetProjects(); 15 | 16 | return projects; 17 | } 18 | 19 | public async Task CreateProject(string projectName) 20 | { 21 | var versionControlDictionary = new Dictionary 22 | { 23 | { "sourceControlType", "Git" } 24 | }; 25 | 26 | var processTemplateDictionary = new Dictionary 27 | { 28 | { "templateTypeId", "6b724908-ef14-45cf-84f8-768b5384da45" } 29 | }; 30 | 31 | var teamProject = new TeamProject 32 | { 33 | Name = projectName, 34 | Description = "This project was created from the sample console application.", 35 | Capabilities = new Dictionary> 36 | { 37 | { "versioncontrol", versionControlDictionary }, 38 | { "processTemplate", processTemplateDictionary } 39 | } 40 | }; 41 | 42 | await GetClient().QueueCreateProject(teamProject); 43 | 44 | Console.WriteLine($"Project '{projectName}' queued for creation..."); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AdoClient/WorkItemsClient.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.TeamFoundation.WorkItemTracking.WebApi; 2 | using Microsoft.VisualStudio.Services.WebApi.Patch; 3 | using Microsoft.VisualStudio.Services.WebApi.Patch.Json; 4 | using System.Threading.Tasks; 5 | using WorkItem = Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem; 6 | 7 | namespace ServicePrincipalsSamples.AdoClient 8 | { 9 | public class WorkItemsClient : AdoClientBase 10 | { 11 | public WorkItemsClient(AdoConnection adoConnection) : base(adoConnection) { } 12 | 13 | public async Task GetWorkItem(int workItemId) 14 | { 15 | return await GetClient().GetWorkItemAsync(workItemId); 16 | } 17 | 18 | public async Task CreateWorkItem(string projectName, string workItemTitle) 19 | { 20 | var patchDocument = new JsonPatchDocument 21 | { 22 | new JsonPatchOperation() 23 | { 24 | Operation = Operation.Add, 25 | Path = "/fields/System.Title", 26 | Value = workItemTitle 27 | } 28 | }; 29 | 30 | return await GetClient().CreateWorkItemAsync(patchDocument, projectName, "Task"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ServicePrincipalsSamples/ClientLibsNET/1-ConsoleApp-AppRegistration/AppMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace ServicePrincipalsSamples 7 | { 8 | public class AppMenu 9 | { 10 | private static int optionsCounter; 11 | 12 | private readonly List optionGroups = new(); 13 | private readonly List