2

This has been giving me headaches for two weeks now and unfortunately neither similar issues nor the docs could enlighten me to full extend.

What I want to achieve

  • I have a simple Teams App with a Web-API that contains data mapped to the user's Microsoft OID.
  • The web API should expect access tokens and return a 401 error if the token is invalid.
  • Thus the User should be able to sign in via the MS identity platform (through SSO for instance).

My understandings

I have set up the usual login process described here and can retrieve an access token and ID token through it. My assumption is that this access token could already be used to authorize with the web API, but expires after an hours or so and has to be re-fetched. This is what Silent Auth or SSO are good for, which appear to be mutually exclusive to me (SSO is easier to achieve but has more limited access).

If that's the case, will SSO suffice for my use case? and furthermore Do I even have to bother with ADAL (or MSAL) then? It's merely used for Silent Auth in the Teams Auth Sample.

If my assumptions are correct so far, I only have few steps left to achieve my goal:

I was taking the Goaltracker sample as reference. In the following extract SSO is used to retrieve a token and sets it in a header for the API. My flow is working similarly, but what bothers me is that the callback never fails, even when I log myself out. I'd suspect that getAuthToken() method uses the id_token (implicitly?) stored by microsoft teams and uses it to retrieve an access token, which can only be done while signed in.

const authTokenRequest = {
            successCallback: (token: string) => {
                if (!config) {
                    config = axios.defaults;
                }
                config.headers["Authorization"] = `Bearer ${token}`;
                resolve(config);
            },
            failureCallback: (error: string) => {
                console.error("Error from getAuthToken: ", error);
                window.location.href = "/signin";
            },
            resources: []
        };
        microsoftTeams.authentication.getAuthToken(authTokenRequest);

From what I can see from the Goaltracker (who doesn't seem to store any retrieved tokens at all), the microsoftTeams.authentication.authenticate() method somehow stores the retrieved ID token itself and uses it to retrieve a new access token when required by calling microsoftTeams.authentication.getAuthToken(). Is that how it works? The returned token's nature is elaborated here, though the answer itself does not clarify whether the retrieved token is an access toke or an ID token).

Further questions

Here is a good explanation on how an access token is different from an ID token (access token is for authorizing on a web API as a bearer token, while ID tokens verify the users identity in the client and should not be used for authorization). With that knowledge, looking at the authentication flow is somewhat confusing as it only labels a 'token' which is returned to the user (probably the ID token) and send to an API as a Bearer token, which would violate it's purpose. So when do each of these tokens come into play here?

Would my proposed implementation in the manner of Goaltracker work? Can i use the ID token to get my required OID and leave the rest to the Teams SDK?

What would be the most basic way to handle the API sided authorization? Such as merely checking whether the token belongs to user in a predefined subset of allowed tenants or if it is not expired yet.

EDIT (nov. 5): As for my last question I used

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
       .AddJwtBearer(options =>
       {
           options.Audience = Configuration["api://{domainFromAppRegistration}/{tenant}"]
           options.Authority = "https://sts.windows.net/{tenant}/";
       });

And simple added the Authorize attribute to my controller and it appears to work (I'm getting a 401 response when calling the API without token). Is that the correct way for multi-tenant applications? I just retrieved the parameters from an earlier access token.

EDIT (nov. 9): I am now aware that microsoft.teams.getAuthToken() actually just returns an ID token for sure. The API also accepts only ID tokens issued by this method, and neither id tokens nor access tokens returned by the regular sign-in endpoint.

EDIT (nov. 17): Several attempts to use the tokens issued through regular sign-in and tampering with the token issuer (replacing the scope="microsoft.graph" with resource or scope= {own resource} didn't work. Setting the former as "openid" actually returned tokens but they were rejected by the authorization middleware.

Beltway
  • 508
  • 4
  • 17
  • 1
    We are looking into it, we will get back to you if we need further information – VaraPrasad-MSFT Nov 23 '20 at 23:15
  • Could you please have a look at this [sample Auth](https://github.com/microsoft/BotBuilder-Samples/tree/main/experimental/teams-sso/csharp_dotnetcore), hope it will esolve your issue. – VaraPrasad-MSFT Dec 01 '20 at 00:51

1 Answers1

0

After weeks of tampering and diving into the depth i could clarify most of the issues as far as necessary for my use case.

Do I even have to bother with ADAL (or MSAL) then?

When using SSO, ADAL or MSAL are not required at all. I furthermore don't recommend using ADAL for new apps as it's EOS as of mid 2022.

On the getAuthToken() API:

It implicitly fetches a token through the client for a registered resource. You can check the method's source code for reference.

/** * Requests an Azure AD token to be issued on behalf of the app. The token is acquired from the cache * if it is not expired. Otherwise a request is sent to Azure AD to obtain a new token. * @param authTokenRequest A set of values that configure the token request. */

This token is i fact an access token, not an ID token. This makes sense if you consider the purpose of SSO: You are required to be logged into Teams for the call to succeed, you wouldn't require an ID token in that case.

The confusing lack of the nonce in the token's header is due to the token being cached. There is currently no work-around to this.

the callback never fails

This makes sense considering the way SSO works. It relies on the user being logged into teams. The seemingly only reason which could cause it to fail is when the user hasn't granted your app permission to retrieve an access token, which includes profile information from MS Graph. (Though this is speculation, this appears to be the reason why all the samples use MS Graph as a resource for the regular flow). This obviously can't be easily reproduced as not everybody has a second tenant with admin permissions to test it.

As for reading the token through the middleware of .NET Core everything works as expected. You can check against more parameters including the signature through Jwt.Bearer.Extensions. However, keep in mind that you can't mitigate token replay as the token will lack the nonce.

Beltway
  • 508
  • 4
  • 17