'How to authenticate to Azure Devops by using token of an Application ID(service principal)?

I have an Azure Devops pipeline where i have automated the creation of a service connection for azure subscriptions and also triggering the stage 2 of the pipeline. For the moment, the pipeline tasks authenticates to Azure Devops by using PAT. I don't want to use PAT because it depends to a person and cannot automate its regeneration. So I have seen that I can authenticate with Azure Active Directory (Azure AD) tokens. Here there is a tutorial of Microsoft. So i have created an app registration and given him to Azure Devops user_impersonation API permission . I have also create azure web app service to run the python application. I have changed the app_config.py with the application ID and the secret:

import os

# To configure this application, fill in your application (client) ID, client secret, 
# AAD tenant ID, and Azure DevOps collection name in the placeholders below.

CLIENT_ID = "xxxxx-xxxx-xxxx-xxxx-xxxx" 
# Application (client) ID of app registration

CLIENT_SECRET = "xxxxxxxxxxxxx"
# In a production app, we recommend you use a more secure method of storing your secret,
# like Azure Key Vault. Or, use an environment variable as described in Flask's documentation:
# https://flask.palletsprojects.com/en/1.1.x/config/#configuring-from-environment-variables
# CLIENT_SECRET = os.getenv("CLIENT_SECRET")
# if not CLIENT_SECRET:
#     raise ValueError("Need to define CLIENT_SECRET environment variable")

# AUTHORITY = "https://login.microsoftonline.com/Enter_the_Tenant_ID_Here"  # For multi-tenant app
AUTHORITY = "https://login.microsoftonline.com/xxxxxx-xxxx-xxxx-xxxx-xxxxx"

REDIRECT_PATH = "/getAToken"  # Used for forming an absolute URL to your redirect URI.
                              # The absolute URL must match the redirect URI you set
                              # in the app's registration in the Azure portal.

ENDPOINT = 'https://vssps.dev.azure.com/myorg/_apis/Tokens/Pats?api-version=6.1-preview' 
# fill in the url to the user's ADO collection name here

SCOPE = ["499b84ac-1321-427f-aa17-267ca6975798/.default"]
# Means "All scopes for the Azure DevOps API resource"

SESSION_TYPE = "filesystem"  
# Specifies the token cache should be stored in server-side session

So the application is running succesfully. But the thing is that the application is asking me to sign in: enter image description here

This is what i wanted to avoid at the beginning. Why it is asking me "login" although i have provided app id and its secret in app_config.py?

UPDATE: I have used client credentials flow as @suomi-dev suggested. I am able to produce a bearer token, i am not able to authenticate to azure devops. Here is the response, i get:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">




<html lang="en-US">

<head>
    <title>

        Azure DevOps Services | Sign In

    </title>
    <meta http-equiv="X-UA-Compatible" content="IE=11;&#32;IE=10;&#32;IE=9;&#32;IE=8" />
    <link rel="SHORTCUT ICON" href="/favicon.ico" />

    <link data-bundlelength="508992" data-bundlename="commoncss"
        data-highcontrast="/_static/tfs/M188_20210614.1/_cssbundles/HighContrast/vss-bundle-commoncss-v6u3Ge4AQ3uP84WaM1_W6QGvjulv0ohK4twlMPGNhjUY="
        data-includedstyles="jQueryUI-Modified;Core;Splitter;PivotView"
        href="/_static/tfs/M188_20210614.1/_cssbundles/Default/vss-bundle-commoncss-vHQmPgPp5spmzbH-BlYoXG_Hn35TPVL28elmFhmVVmDU="
        rel="stylesheet" />
    <link data-bundlelength="116278" data-bundlename="viewcss"
        data-highcontrast="/_static/tfs/M188_20210614.1/_cssbundles/HighContrast/vss-bundle-viewcss-vfceofgWpooCKNRb2b7qGygIv-1cQRzxrP0B4pEtwImw="
        data-includedstyles="VSS.Controls"
        href="/_static/tfs/M188_20210614.1/_cssbundles/Default/vss-bundle-viewcss-vXgT3Z4VlQs_ppchNra9ZkzNQE6bqAzibhdP4ooPkmWU="
        rel="stylesheet" />

    <!--UxServices customizations -->

    <link href="/_static/tfs/M188_20210614.1/_content/Authentication.css" type="text/css" rel="stylesheet" />

</head>

<body class="platform">

    <script type="text/javascript">
        var __vssPageContext = {"webContext":{"user":{"id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","name":"Anonymous","email":"","uniqueName":"TEAM FOUNDATION\\Anonymous"},"host":{"id":"7d0cc8e9-39e8-4313-9ff3-23ace0f8f4cf","name":"TEAM FOUNDATION","uri":"https://spsprodweu4.vssps.visualstudio.com/","relativeUri":"/","hostType":"deployment","scheme":"https","authority":"spsprodweu4.vssps.visualstudio.com"}},"moduleLoaderConfig":{"baseUrl":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/","paths":{"Profile/Scripts/Resources":"en-US","VSS/Resources":"en-US","Account/Scripts/Resources":"en-US","UserManagement/Scripts/Resources":"en-US","Authentication/Scripts/Resources":"en-US"},"map":{},"contributionPaths":{"VSS":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/VSS","pathType":"default"},"VSS/Resources":{"value":"en-US","pathType":"resource"},"q":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/q","pathType":"default"},"knockout":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/knockout","pathType":"default"},"mousetrap":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/mousetrap","pathType":"default"},"mustache":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/mustache","pathType":"default"},"react":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/react.15.3","pathType":"default"},"react-dom":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/react-dom.15.3","pathType":"default"},"react-transition-group":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/react-transition-group.15.3","pathType":"default"},"jQueryUI":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/jQueryUI","pathType":"default"},"jquery":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/jquery","pathType":"default"},"OfficeFabric":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/OfficeFabric","pathType":"default"},"tslib":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/tslib","pathType":"default"},"@uifabric":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/@uifabric","pathType":"default"},"VSSUI":{"value":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/VSSUI","pathType":"default"}},"shim":{"jquery":{"deps":[],"exports":"jQuery"}},"waitSeconds":30},"coreReferences":{"stylesheets":[{"url":"/_static/tfs/M188_20210614.1/_cssbundles/Default/vss-bundle-ext-core-css-vNmgDWCLCX0sHlt44FFqt9QrKVBLhz4rqsgP12zXSp8g=","highContrastUrl":null,"isCoreStylesheet":true}],"scripts":[{"identifier":"JQuery","url":"/_static/3rdParty/_scripts/jquery-2.2.4.min.js","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":true},{"identifier":"JQueryXDomain","url":"/_static/3rdParty/_scripts/jquery.xdomainrequest.min.js","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":true},{"identifier":"Promise","url":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/promise.js","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":true},{"identifier":"GlobalScripts","url":"/_static/tfs/M188_20210614.1/_scripts/TFS/min/global-scripts.js","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":true},{"identifier":"LoaderFixes","url":"/_static/tfs/M188_20210614.1/_scripts/TFS/pre-loader-shim.min.js","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":false},{"identifier":"AMDLoader","url":"/_static/3rdParty/_scripts/require.min.js","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":true},{"identifier":"LoaderFixes","url":"/_static/tfs/M188_20210614.1/_scripts/TFS/post-loader-shim.min.js","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":false}],"coreScriptsBundle":{"identifier":"CoreBundle","url":"/_public/_Bundling/Content?bundle=vss-bundle-basejs-v9GpWWBnsWqhM23ijhK2HfAqLowTXGUqZLDRsBCZbkfY=","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":true},"extensionCoreReferences":{"identifier":"CoreBundle","url":"/_public/_Bundling/Content?bundle=vss-bundle-ext-core-vIzSYLPJCTdYZH2THb6MmQ8IGqUykR9674vJI83Yv4xM=","fallbackUrl":null,"fallbackCondition":null,"isCoreModule":true}},"webAccessConfiguration":{"isHosted":true,"paths":{"rootPath":"/","staticContentRootPath":"/","staticContentVersion":"M188_20210614.1","resourcesPath":"/_static/tfs/M188_20210614.1/_content/","staticRootTfs":"/_static/tfs/M188_20210614.1/","cdnFallbackStaticRootTfs":"/_static/tfs/M188_20210614.1/","staticRoot3rdParty":"/_static/3rdParty/"},"api":{"webApiVersion":"1","areaPrefix":"_","controllerPrefix":""},"mailSettings":{"enabled":false},"registryItems":{}},"microsoftAjaxConfig":{"cultureInfo":{"name":"en-US","numberFormat":{"CurrencyDecimalDigits":2,"CurrencyDecimalSeparator":".","IsReadOnly":true,"CurrencyGroupSizes":[3],"NumberGroupSizes":[3],"PercentGroupSizes":[3],"CurrencyGroupSeparator":",","CurrencySymbol":"$","NaNSymbol":"NaN","CurrencyNegativePattern":0,"NumberNegativePattern":1,"PercentPositivePattern":1,"PercentNegativePattern":1,"NegativeInfinitySymbol":"-∞","NegativeSign":"-","NumberDecimalDigits":2,"NumberDecimalSeparator":".","NumberGroupSeparator":",","CurrencyPositivePattern":0,"PositiveInfinitySymbol":"∞","PositiveSign":"+","PercentDecimalDigits":2,"PercentDecimalSeparator":".","PercentGroupSeparator":",","PercentSymbol":"%","PerMilleSymbol":"‰","NativeDigits":["0","1","2","3","4","5","6","7","8","9"],"DigitSubstitution":1},"dateTimeFormat":{"AMDesignator":"AM","Calendar":{"MinSupportedDateTime":"0001-01-01T00:00:00","MaxSupportedDateTime":"9999-12-31T23:59:59.9999999","AlgorithmType":1,"CalendarType":1,"Eras":[1],"TwoDigitYearMax":2029,"IsReadOnly":true},"DateSeparator":"/","FirstDayOfWeek":0,"CalendarWeekRule":0,"FullDateTimePattern":"dddd, MMMM d, yyyy h:mm:ss tt","LongDatePattern":"dddd, MMMM d, yyyy","LongTimePattern":"h:mm:ss tt","MonthDayPattern":"MMMM d","PMDesignator":"PM","RFC1123Pattern":"ddd, dd MMM yyyy HH':'mm':'ss 'GMT'","ShortDatePattern":"M/d/yyyy","ShortTimePattern":"h:mm tt","SortableDateTimePattern":"yyyy'-'MM'-'dd'T'HH':'mm':'ss","TimeSeparator":":","UniversalSortableDateTimePattern":"yyyy'-'MM'-'dd HH':'mm':'ss'Z'","YearMonthPattern":"MMMM yyyy","AbbreviatedDayNames":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"ShortestDayNames":["Su","Mo","Tu","We","Th","Fr","Sa"],"DayNames":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"AbbreviatedMonthNames":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",""],"MonthNames":["January","February","March","April","May","June","July","August","September","October","November","December",""],"IsReadOnly":true,"NativeCalendarName":"Gregorian Calendar","AbbreviatedMonthGenitiveNames":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",""],"MonthGenitiveNames":["January","February","March","April","May","June","July","August","September","October","November","December",""]},"numberShortForm":{"QuantitySymbols":["K","M","B"],"NumberGroupSize":1000,"ThousandSymbol":"K"},"eras":null}},"timeZonesConfiguration":{},"featureAvailability":{"featureStates":{"VisualStudio.Services.Contribution.EnableOnPremUnsecureBrowsers":false,"VisualStudio.Service.WebPlatform.ClientErrorReporting":false,"Microsoft.VisualStudio.Services.Gallery.Client.UseCdnAssetUri":false,"VisualStudio.Services.WebAccess.SubresourceIntegrity":false,"VisualStudio.Services.IdentityPicker.ReactProfileCard":true}},"appInsightsConfiguration":{"enabled":false,"instrumentationKey":"00000000-0000-0000-0000-000000000000","insightsScriptUrl":null},"diagnostics":{"sessionId":"91ddbf13-332e-4719-965a-6d5f16a576b5","activityId":"91ddbf13-332e-4719-965a-6d5f16a576b5","bundlingEnabled":true,"webPlatformVersion":"M188","serviceVersion":"Dev18.M188.1 (build: AzureDevOps_M188_20210614.1)"},"navigation":{"topMostLevel":"deployment","area":"","currentController":"Signin","currentAction":"Index","routeId":"LegacyWebAccessRoute","routeValues":{"controller":"Signin","action":"Index"}},"globalization":{"explicitTheme":"","theme":"Default","culture":"en-US","timezoneOffset":0,"timeZoneId":"UTC"},"serviceInstanceId":"951917ac-a960-4999-8464-e3f0aa25b381","hubsContext":{},"serviceLocations":{"locations":{"951917ac-a960-4999-8464-e3f0aa25b381":{"Application":"https://spsprodweu4.vssps.visualstudio.com/","Deployment":"https://spsprodweu4.vssps.visualstudio.com/"}}}};
    </script>
    <script type="text/javascript">
        var __cultureInfo = __vssPageContext.microsoftAjaxConfig.cultureInfo;
    </script>
    <script type="text/javascript">
        if (window.performance && window.performance.mark) { window.performance.mark('startLoadBundleOuter-basejs'); }
    </script>
    <script data-bundlelength="125076" data-bundlename="basejs"
        src="/_public/_Bundling/Content?bundle=vss-bundle-basejs-v9GpWWBnsWqhM23ijhK2HfAqLowTXGUqZLDRsBCZbkfY="
        type="text/javascript"></script>
    <script type="text/javascript">
        if (window.performance && window.performance.mark) { window.performance.mark('endLoadBundleOuter-basejs'); }
    </script>


    <script type="text/javascript">
        require.config(__vssPageContext.moduleLoaderConfig);
    </script>

    <input name="__RequestVerificationToken" type="hidden" value="4n1E6rTOw8fc0z_SO8fzjPWwH2GntNK14JNwSy_b6-63D4AUIHexBvld_xy8utkCB5esKsyGLQA-05YaMPklaRnZ1881" />






    <div class="account signin main-container hide">


        <div class="page-content">
            <div class="header-section" role="banner">


                <link rel="stylesheet" type="text/css" href="/_static/tfs/M188_20210614.1/_content/Combined.css">
                <div id="ux-header" class="FF ltr vsIntegrate" xmlns="http://www.w3.org/1999/xhtml">
                    <span id="isMobile"></span>
                    <div class="upperBand">
                        <div class="upperBandContent">
                            <div class="left"></div>
                            <div class="right">
                                <div class="profileImage"></div>

                                <div id="signIn">
                                    <a class=":SignedOutProfileElement: createProfileLink" href="/go/profile"
                                        title="Anonymous">Anonymous</a>
                                    <a class="scarabLink" href="/_signout">Sign out</a>
                                </div>
                            </div>
                            <div class="clear-both"></div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="content-section" role="main">


                <div class="signin-main-content">
                    <noscript>
                        <span class="error">Microsoft Internet Explorer&#39;s Enhanced Security Configuration is currently enabled on your environment. This enhanced level of security prevents our web integration experiences from displaying or performing correctly. To continue with your operation please disable this configuration or contact your administrator.</span>
                    </noscript>
                    <div class="provider-control">
                        <script class="options" defer="defer" type="application/json">
                            {"providerOptions":{"force":false,"orgIdAuthUrl":"https://login.microsoftonline.com/c611881e-e32d-4009-accc-0c1a258b602a/oauth2/authorize?client_id=499b84ac-1321-427f-aa17-267ca6975798\u0026site_id=501454\u0026response_mode=form_post\u0026response_type=code+id_token\u0026redirect_uri=https%3A%2F%2Fspsprodweu4.vssps.visualstudio.com%2F_signedin\u0026nonce=dae7c29c-fa33-46da-a7d8-3bcbe135a55b\u0026state=realm%3Ddev.azure.com%26reply_to%3Dhttps%253A%252F%252Fdev.azure.com%252Ftest%252Flab-test%252F_apis%252Fgit%252Frepositories%253Fapi-version%253D6.1-preview.1%26ht%3D2%26hid%3De82482f2-c6c4-429b-bf8a-20a4b358da6f%26nonce%3Ddae7c29c-fa33-46da-a7d8-3bcbe135a55b\u0026resource=https%3A%2F%2Fmanagement.core.windows.net%2F\u0026cid=dae7c29c-fa33-46da-a7d8-3bcbe135a55b\u0026wsucxt=1","user":null,"signInContext":"eyJodCI6MiwiaGlkIjoiNWRhZWZkMmEtOGZhNS00NDFlLTgyZWItYjM3OTUwODcxNmZmIiwicXMiOnsicmVhbG0iOiJkZXYuYXp1cmUuY29tIiwicmVwbHlfdG8iOiJodHRwczovL2Rldi5henVyZS5jb20vdGVzdGRhdGF5L2xhYnYyLXRlc3QvX2FwaXMvZ2l0L3JlcG9zaXRvcmllcz9hcGktdmVyc2lvbj02LjEtcHJldmlldy4xIiwiaHQiOiIyIiwiaGlkIjoiZTgyNDgyZjItYzZjNC00MjliLWJmOGEtMjBhNGIzNThkYTZmIiwibm9uY2UiOiJkYWU3YzI5Yy1mYTMzLTQ2ZGEtYTdkOC0zYmNiZTEzNWE1NWIifSwicnIiOiIiLCJ2aCI6IiIsImN2IjoiIiwiY3MiOiIifQ2"}}
                        </script>
                    </div>
                </div>


            </div>


        </div>





        <script type="text/javascript">
            if (window.performance && window.performance.mark) { window.performance.mark('startLoadBundleOuter-common'); }
        </script>
        <script data-bundlelength="212619" data-bundlename="common" data-includedscripts="VSS/Bundling"
            src="/_public/_Bundling/Content?bundle=vss-bundle-common-v9clbQjZeX8SFazwgF3HBRpCw_AYcSBPxtOpkg9Bv2-o="
            type="text/javascript"></script>
        <script type="text/javascript">
            if (window.performance && window.performance.mark) { window.performance.mark('endLoadBundleOuter-common'); }
        </script>
        <script type="text/javascript">
            if (window.performance && window.performance.mark) { window.performance.mark('startLoadBundleOuter-view'); }
        </script>
        <script data-bundlelength="159354" data-bundlename="view"
            data-includedscripts="Authentication/Scripts/SPS.Authentication.Controls;Authentication/Scripts/SPS.Authentication"
            src="/_public/_Bundling/Content?bundle=vss-bundle-view-vNUGsoXgRUC5b3kr3BUOKazaVcS2VmFUCbEtaFD5SGSA="
            type="text/javascript"></script>
        <script type="text/javascript">
            if (window.performance && window.performance.mark) { window.performance.mark('endLoadBundleOuter-view'); }
        </script>

        <script type="text/javascript">
            if (window.performance && window.performance.mark) { window.performance.mark('requireStart'); }
require(["Authentication/Scripts/SPS.Authentication.Controls","Authentication/Scripts/SPS.Authentication"], function(){  if (window.performance && window.performance.mark) { window.performance.mark('requireEnd'); } window.requiredModulesLoaded=true;  });
        </script>

</body>

</html> 

It asks me to sign in. But when i try the same GET request with my PAT, it is working.



Solution 1:[1]

If you want to access your resource (API endpoint) purely using client id and secret without user authentication, then you are looking for client credentials flow (also known as two legged OAuth). You will also need to give access & roles to your client (app registration) either using ACL or application permission assignment through AD.

Once you implement client credentials flow, you will get access token. Then you can use that access token in your subsequest endpoint requests.

I have not done this in python but the concept is same. You can also test this with POSTMAN:

// Obtain token using client credentials flow
POST //Line breaks for clarity
https://login.microsoftonline.com/{tenentId}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id=xxxxxxxxxx
&scope=api://499b84ac-1321-427f-aa17-267ca6975798/.default
&client_secret=xxxxxxx
&grant_type=client_credentials

Above request would give you access token in the reponse body. Then you can use the access token in your subsequent endpoint requests.

This article describes it in more detail: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow

Solution 2:[2]

Download ms-identity-python-webapp and follow the install instructions.

Add msal, azure-devops and msrest to requirements.txt

msal>=1.7,<2
azure-devops==6.0.0b2
msrest==0.6.13

In app_config.py change the SCOPE to user_impersonation and add your ADO_URL

SCOPE = ["499b84ac-1321-427f-aa17-267ca6975798/user_impersonation"]
DEVOPS_URL = 'https://dev.azure.com/<your ADO organisation name here>' 

In your app.py try the following in graphcall()

from azure.devops.connection import Connection
from msrest.authentication import OAuthTokenAuthentication

# Use the MSAL token to generate an ADO OAuth token
credentials = OAuthTokenAuthentication(app_config.CLIENT_ID, token)
connection = Connection(base_url=app_config.DEVOPS_URL, creds=credentials)

# Get a client
core_client = connection.clients.get_core_client()
wit_client = connection.clients.get_work_item_tracking_client()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 suomi-dev
Solution 2 Matt Melton