Azure / azure-sdk

This is the Azure SDK parent repository and mostly contains documentation around guidelines and policies as well as the releases for the various languages supported by the Azure SDK.
http://azure.github.io/azure-sdk
MIT License
486 stars 296 forks source link

Board Review: Azure Communication Services (SPOOL) Auth CTE (JS, Python, .NET, Java) #4272

Closed garchiro7 closed 2 years ago

garchiro7 commented 2 years ago

Metadata

Service team responsible for the client library:

Responsible:

Name of the client library:

Docs:

Timeline:

Link to the service REST APIs:

API Views

Changes

Compared to Public Preview, we're adding two parameters to the getTokenForTeamsUser() method. On top of existing token parameter, we are adding two new parameters - appId and userId allowing Contoso to specify the AAD user and app that are allowed to perform the token exchange.

Problem definition

Contoso sends a PFT token obtained on the side of their user to ACS Auth. Because the PFT is encrypted, Contoso can’t verify its content and confirm that it belongs to their AAD app registration. It would be possible that a malicious actor could use an access token issued for a different 3P app or a different user which poses two main concerns:

Problem mitigation

Adding the following two parameters to the endpoint so that ACS Auth service can verify them:

Champion scenarios

This is the code representation of the scheme above:

CLIENT:

async function getCommunicationTokenForTeamsUser() {
  // Acquire a token with a custom scope for Contoso's 3P AAD app
  let apiAccessToken = await acquireAadToken({ scopes: ["api://1875691f-131f-4802-95a5-4511bde1408e/Contoso.CustomScope"] })

  // Acquire a token with a delegated permission Teams.ManageCalls for CTE's 1P AAD app
  let teamsUserAccessToken = await acquireAadToken({ scopes: ["https://auth.msft.communication.azure.com/Teams.ManageCalls"] });

  // Call your API with token
  if (apiAccessToken !== null && teamsUserAccessToken !== null) {
    fetch("/exchange", {
      method: "POST",
      // Use API access token for authentication
      headers: [["Authorization", `Bearer ${apiAccessToken}`], ["Content-Type", "application/json"]],
      // Use Teams user access token as payload
      body: JSON.stringify({ accessToken: teamsUserAccessToken })
    })
      .then(response => response.json())
      .then(response => {
        if (response) {
          logMessage(JSON.stringify(response));
        }
      })
      .catch(error => {
        console.log(error);
      });
  }
}

SERVER:

app.use(express.static('App'));

app.post('/exchange',
    jwt({ aadIssuerUrlTemplate: 'https://login.microsoftonline.com/{tenantId}/v2.0' }), // Verify JWT integrity
    jwtScope('Contoso.CustomScope', { scopeKey: 'scp' }), // Verify required scopes
    async (req, res, next) => {

        try {
            // Get Azure AD App client id
            const appId = process.env.AAD_CLIENT_ID;

            // Get user's oid
            const userId = req.user.oid;

            // Create a new CommunicationIdentityClient
            const identityClient = new CommunicationIdentityClient(COMMUNICATION_SERVICES_CONNECTION_STRING);

            // Pass the Client ID and oid
            let communicationIdentityToken = await identityClient.getTokenForTeamsUser(req.body.accessToken, appId, userId);

            res.status(200).send(communicationIdentityToken);
        }
        catch (err) {
            next(err);
        }
    });

Sample apps

GH repo: https://github.com/petrsvihlik/azure-communication-identity-cte-samples

lilyjma commented 2 years ago

scheduled for 5/12 9-11am pst

mpodwysocki commented 2 years ago

For clarity, can we not mix async/await and then such as the following?

async function getCommunicationTokenForTeamsUser() {
  // Acquire a token with a custom scope for Contoso's 3P AAD app
  let apiAccessToken = await acquireAadToken({ scopes: ["api://1875691f-131f-4802-95a5-4511bde1408e/Contoso.CustomScope"] })

  // Acquire a token with a delegated permission Teams.ManageCalls for CTE's 1P AAD app
  let teamsUserAccessToken = await acquireAadToken({ scopes: ["https://auth.msft.communication.azure.com/Teams.ManageCalls"] });

  // Call your API with token
  if (apiAccessToken !== null && teamsUserAccessToken !== null) {
    try {
        const response = await fetch("/exchange", {
            method: "POST",
            // Use API access token for authentication
            headers: [["Authorization", `Bearer ${apiAccessToken}`], ["Content-Type", "application/json"]],
            // Use Teams user access token as payload
            body: JSON.stringify({ accessToken: teamsUserAccessToken })
        });
        const json = await response.json();
        if (json) {
            logMessage(JSON.stringify(json));
        }
    catch (error) {
        console.log(error);
    }
}
petrsvihlik commented 2 years ago

@mpodwysocki absolutely, the code was grabbed from my dirty PoC which I just started converting to async/await for better readability. we'll take your comment into consideration when polishing the final samples. thanks for pointing it out!

tg-msft commented 2 years ago

Recording (MS INTERNAL ONLY)

petrsvihlik commented 2 years ago

all api views approved & PRs merged