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:
- 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'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>
);
}
- 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'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.
- 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.