0

I'm working on a tab app where I intend using an on-behalf-of flow to obtain access token from azure active directory, so as to request data from Microsoft graph endpoints, and implementing this requires a client secret.

Is there a way I can get the client secret in a teams toolkit project just like I can get an application ID?

(Update) Details of what I'm trying to do

I'm working on an app where I would be calling Microsoft graph endpoints (protected by azure ad) to get data. The challenge I'm facing currently is how to handle authentication in a Teams tab app project created using Microsoft Teams Toolkit, so as to obtain an access token to request data from the graph endpoints or create an authenticated graph client.

What I have tried:

  1. I have tried the code below, using the teamsfx.login() function within the react component where I'm calling a protected graph endpoint. But whenever I click the button to initiate a graph call, there is always a pop-up flash.
export const GraphEmail: React.FC = () => {
    const [messages, setMessages] = useState<any[]>([]);

    const handleGetMyMessagesOnClick = async (event: any): Promise<void> => {
        await getMessages();
    };

    const getMessages = async (promptConsent: boolean = false): Promise<void> => {
        const teamsfx = new TeamsFx();
        await teamsfx.login(["User.Read", "Mail.Read"]);
        const graphClient = createMicrosoftGraphClient(teamsfx, ["User.Read", "Mail.Read"]);
        

        await graphClient
            .api("/me/messages")
            .select(["receivedDateTime", "subject"])
            .top(15)
            .get(async (error: any, rawMessages: any, rawResponse?: any) => {
                if (!error) {
                    setMessages(rawMessages.value);
                    Promise.resolve();
                } else {
                    console.error("graph error", error);
                }
            });
    };

    return (
        <Flex column gap="gap.small">
            <Header>Recent messages in current user&apos;s mailbox</Header>
            <Button primary
                content="Get My Messages"
                onClick={handleGetMyMessagesOnClick}></Button>
            <List selectable>
                {
                    messages.map((message, i) => (
                        <List.Item key={i} media={<EmailIcon></EmailIcon>}
                            header={message.receivedDateTime}
                            content={message.subject} index={i}>
                        </List.Item>
                    ))
                }
            </List>
        </Flex>
    );
}
  1. In order to remove the consistent flash after the first popup for the actual login, since the user is already logged-in during the first button click, I made the changes below (idea gotten from the useGraph() component code on GitHub). But then I got an "uncaught (in promise) undefined" error when the button is clicked. The console logs are displayed below too.
export const NewGraphEmail: React.FC = () => {
    const [needConsent, setNeedConsent] = useState(false);
    const [messages, setMessages] = useState<any[]>([]);

    const handleGetMyMessagesOnClick = async (event: any): Promise<void> => {
        await getMessages();
    };

    const getMessages = async (promptConsent: boolean = false): Promise<void> => {
        const teamsfx = new TeamsFx();
        const scope = ["User.Read", "Mail.Read"];

        if (needConsent) {
            try {
                await teamsfx.login(scope);
                setNeedConsent(false);
                // Important: tokens are stored in sessionStorage, read more here: https://aka.ms/teamsfx-session-storage-notice
            } catch (err: unknown) {
                if (err instanceof ErrorWithCode && err.message?.includes("CancelledByUser")) {
                    const helpLink = "https://aka.ms/teamsfx-auth-code-flow";
                    err.message +=
                        '\nIf you see "AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application" ' +
                        "in the popup window, you may be using unmatched version for TeamsFx SDK (version >= 0.5.0) and Teams Toolkit (version < 3.3.0) or " +
                        `cli (version < 0.11.0). Please refer to the help link for how to fix the issue: ${helpLink}`;
                }
                throw err;
            }
        }

        const graphClient = createMicrosoftGraphClient(teamsfx, scope);
        await graphClient
            .api("/me/messages")
            .select(["receivedDateTime", "subject"])
            .top(15)
            .get(async (error: any, rawMessages: any, rawResponse?: any) => {
                if (!error) {
                    setMessages(rawMessages.value);
                    Promise.resolve();
                } else if (error instanceof GraphError && error.code?.includes("UiRequiredError")) {
                    // Silently fail for user didn't consent error
                    setNeedConsent(true);
                    // getMessages();
                } else {
                    console.log("graph error", error);
                }
            });
    };

    return (
        <Flex column gap="gap.small">
            <Header>Recent messages in current user&apos;s mailbox</Header>
            <Button primary
                content="Get My Messages"
                onClick={handleGetMyMessagesOnClick}></Button>
            <List selectable>
                {
                    messages.map((message, i) => (
                        <List.Item key={i} media={<EmailIcon></EmailIcon>}
                            header={message.receivedDateTime}
                            content={message.subject} index={i}>
                        </List.Item>
                    ))
                }
            </List>
        </Flex>
    );
}

The logs in the browser console

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Info - Create 
Microsoft Graph Client useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Info - Create 
Microsoft Graph Authentication Provider with scopes: 'User.Read Mail.Read' 
useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Info - Get Graph 
Access token with scopes: 'User.Read Mail.Read'
useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Info - Create teams 
user credential useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Verbose - Validate 
authentication configuration
useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Info - Get access 
token with scopes: User.Read Mail.Read
useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Verbose - Get SSO 
token from memory cache
useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:00 GMT] : @microsoft/teamsfx : Verbose - Failed to 
call acquireTokenSilent. Reason: no_account_error: No account object 
provided to acquireTokenSilent and no active account has been set. Please 
call setActiveAccount or provide an account on the request.. 

authorize:74 BSSO Telemetry: 
{"result":"Error","error":"NoExtension","type":"ChromeSsoTelemetry","data":
{},"traces":["BrowserSSO Initialized","Creating ChromeBrowserCore 
provider","Sending message for method CreateProviderAsync","Received message 
for method CreateProviderAsync","Error: ChromeBrowserCore error NoExtension: 
Extension is not installed."]}

DevTools failed to load source map: Could not load content for 
https://login.microsoftonline.com/5d2e66da-54ba-4897-82ee-
60eeb8ce5994/oauth2/v2.0/4616d84a89b332161726.map: HTTP error: status code 
404, net::ERR_HTTP_RESPONSE_CODE_FAILURE
useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:02 GMT] : @microsoft/teamsfx : Verbose - Failed to 
call ssoSilent. Reason: login_required: AADSTS50058: A silent sign-in 
request was sent but no user is signed in.
Trace ID: 5043daaa-b142-4083-9ad9-a798c2303b00
Correlation ID: ce16ec27-0261-423e-96f6-810344f76647
Timestamp: 2022-08-09 10:22:03Z. 
useTeamsFx.js:34 

[Tue, 09 Aug 2022 10:22:02 GMT] : @microsoft/teamsfx : Error - Failed to get 
access token cache silently, please login first: you need login first before 
get access token.
TestGraph.tsx:16 

Uncaught (in promise) undefined

The uncaught error points to the end of the "handleGetMyMessagesOnClick" function above.

  1. The other options:
  • The useGraph() hook: I would have loved to use the hook directly, but it seem to be suited for cases when using microsoft graph toolkit components, which won't serve my purpose during the project.

  • The on-behalf-of flow: I believe this would have solved the problem, following the steps in this video from the Microsoft 365 Developer channel, but the solution requires having an azure ad app client secret, which I don't know how to get in a microsoft teams toolkit project, since microsoft teams toolkit handles azure ad app registration.

akanjimm
  • 1
  • 1
  • Teams toolkit generates a *client side* solution - one that goes down compeltely to the end user's browser. As a result, putting the client application id AND the client **secret** in there would completely destroy the idea of the security that an Azure Application is seeking to provide you. Perhaps update your question with what you are trying to *do* and we can provide an answer on how to do *that* instead. – Hilton Giesenow Aug 08 '22 at 16:07
  • Thanks @HiltonGiesenow. I intend creating a backend code where the values would be used. I would also update my question with details of what I'm trying to do shortly. – akanjimm Aug 09 '22 at 13:59

3 Answers3

0

A Client secret is a password to optain an access token though an API.

You need to implement an API than kan exchange an Teams SSO token, for a MS Graph API access token using a client secret (on-behalf-of). This client secret must never be exposed to the user/client, and should be secret; hence the name.

See this for a detailed explaination.

tauzN
  • 5,162
  • 2
  • 16
  • 21
  • Thanks @TauzN. This would have been nice, but I don't know how to get the client secret in a project created using Microsoft Teams Toolkit, which I'm going to use in the on-behalf-of flow to exchange the Teams SSO token for an access token. Please check my question for an update. – akanjimm Aug 09 '22 at 14:13
  • Go to your application in Azure Portal – tauzN Aug 11 '22 at 08:33
0

What you're wanting in this case is an "on behalf of" token from Graph, which lets you make calls to graph from your app as if it was the user ("on behalf of" the user) and it seems reasonable enough at first to do this in your client-side code. However, it turns out this isn't actually secure because it means the user's token is flying around almost in the open. As a result, it's better to create your own backend API (e.g. in an Azure Function) and make the "on behalf of" ("OBO") call from within there. Teams Toolkit actually creates some structure to help with this backend API, I think.

I'm not sure how well it covers Teams Toolkit (it's a while since I last watched it), but this video is an excellent overview: https://www.youtube.com/watch?v=kruUnaZgQaY . See here also for info: https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/visual-studio-code-tab-sso

Hilton Giesenow
  • 9,809
  • 2
  • 10
  • 24
  • Thanks @Hilton Giesenow, I will check out the links. – akanjimm Aug 09 '22 at 14:16
  • I have checked out the links. The video didn't cover Teams Toolkit and the solution also requires a client secret. The article didn't address the issue too. – akanjimm Aug 10 '22 at 07:46
  • yes you definitely need the secret. Do you have one? It's easy to create from the Azure portal. – Hilton Giesenow Aug 10 '22 at 09:04
  • No, I don't have one. The challenge I currently have is, if I do get one, how do I integrate it into my teams toolkit project? – akanjimm Aug 10 '22 at 13:07
  • Do you think it would work out if I create a new client secret under the azure ad app created by teams toolkit, have that saved in a .env file, then use it in my backend code? – akanjimm Aug 10 '22 at 13:15
  • so you can definitely create a new one, if you don't have the details for the one that's there - that's no problem. With regards your env file though, is your backend a node app? I ask because if you mean instead that it's part of your front end project (e.g. React app), then remember the secret should NOT be in there. Anything in the env file will get encoded into the app itself, and available for download by an end user. – Hilton Giesenow Aug 10 '22 at 17:21
  • Yes, I would be creating a node backend. I'd create a new client secret and try it out. Thanks. – akanjimm Aug 10 '22 at 21:45
0

In a Teams Toolkit project, you can obtain a client secret by following these steps:

Open the Azure portal (portal.azure.com) and navigate to your Azure Active Directory (AAD) tenant.

In the Azure Active Directory menu, click on "App registrations".

Locate and select your Teams Toolkit project's application registration.

In the application's overview page, scroll down to the "Client secrets" section and click on "New client secret".

Provide a description for the client secret and choose an expiration option (e.g., "Never", "In 1 year", etc.).

Click on "Add" to generate the client secret.

Once the client secret is generated, copy it and securely store it. Note that the client secret is only displayed once, so make sure to save it in a secure location.

Now you have obtained the client secret for your Teams Toolkit project's application registration. You can use this client secret in your code to authenticate and obtain an access token using on behalf of flow with Azure Active Directory.

If you want to get it in a running project, you can fetch it from the environment variable. You can add the value in env as a parameter or can extract using process.env. Define the path to your environment variable file and use it from there.

IAM5K
  • 257
  • 3
  • 10
  • IAM5K - As commented on other answers, your last six or so here appear likely to be entirely or partially written by AI (e.g., ChatGPT). Please be aware that [posting AI-generated content is not allowed here](//meta.stackoverflow.com/q/421831). If you used an AI tool to assist with any answer, I would encourage you to delete it. Thanks! – NotTheDr01ds Jul 13 '23 at 19:38
  • **Readers should review this answer carefully and critically, as AI-generated information often contains fundamental errors and misinformation.** If you observe quality issues and/or have reason to believe that this answer was generated by AI, please leave feedback accordingly. – NotTheDr01ds Jul 13 '23 at 19:39