I'm writing a web API. One of the things it does is hit Azure DevOps using the Team Foundation Core DLLs. As we are backed by Azure AD (Azure Active Directory) I was thinking that I could have my app authenticate against Azure AD and use that token/authorization for Azure DevOps. This isn't the only service that it is going to authenticate with. Can I do this? what other ways can I achieve this goal? I don't want to prompt the user to authorize against Azure AD each time it goes to hit a unique service, especially as they are all backed by Azure AD.
问题:
回答1:
Yes, you can do this.
Note: I'm assuming your API is secured by Azure AD, and that in order to call your API, users need to sign in to the client of your API with Azure AD.
Let's say you wanted your API to make requests not only to Azure DevOps, but also to Microsoft Graph (to take an example of another API secured by Azure AD--this could of course be any other API, including a second API of your own), and that you wanted those requests to be on behalf of the signed-in user. That is, on behalf of the user who is identified in the access token received by the API.
You could do the following (diagram below):
- A user signs in with Azure AD to the client application, and the client application requests and access token for your API.
- The client app presents this access token to your API when making any API requests (e.g. in the Authorization header), and your API does all the necessary validations.
- Your API takes the access token it received, and presents it to Azure AD, requesting a new access token "on behalf of" the signed-in user from the presented access token, but for a different resource: Azure DevOps. Assuming all the right permissions and consent are in place, Azure AD responds to the API with an access token for Azure DevOps.
- The API presents this access token when making requests to Azure DevOps.
- Your API also wants to call Microsoft Graph (e.g. to get more details about to user, or to send an email or something), so the API again goes to Azure AD, presenting the access token it received in (2), asking for a token to Microsoft Graph. If consent and permissions check out, Azure AD complies.
- Your API uses this third access token when making requests to Microsoft Graph.
+--------+ +-----------+ +-----------------+
(User)+---> +-(2)--> +-(4)---> |
| Client | | Your API <-------+ Azure DevOps |
| <------+ | | |
+----^---+ | +-(6)+ +-----------------+
| | | <--+ |
| | +---^----^--+ | | +-----------------+
(1) (3) (5) | +--> |
| | || || +----+ Microsoft Graph |
| | +--v----v--+ | (or other API) |
| +--------> | | |
| | Azure AD | +-----------------+
+----------+ |
+----------+
The detailed token flow is described in the Azure AD documentation (for both the v1 endpoint and the v2 endpoint).
Of course, all the complexities here, as well as token caching and refreshing, should be handled by simple libraries such as ADAL or MSAL, both of which have wiki pages for the on-behalf-of flow (with ADAL, with MSAL). Here's a summarized example of what it looks like with ADAL (taken from the To
// Use ADAL to get a token On Behalf Of the current user. To do this we will need:
// The Resource ID of the service we want to call.
// The current user's access token, from the current request's authorization header.
// The credentials of this application.
// The username of the user calling the API
//
string resourceId = "499b84ac-1321-427f-aa17-267ca6975798"; // this is the Azure DevOps app ID
string userName = "...";// get from incoming token
string userAccessToken = "..." // from incoming Authorization header;
UserAssertion userAssertion = new UserAssertion(userAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
ClientCredential clientCred = new ClientCredential(clientId, appKey);
AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache);
// Now make the on-behalf-of request
result = await authContext.AcquireTokenAsync(resourceId, clientCred, userAssertion);
accessToken = result.AccessToken; // <-- this is a token for Azure DevOps!