Teams Bot Auth: oAuth card prompts for login every hour with AADV2

Viewed 678

I'm following the teams auth sample available here. To sign in into the bot using oAuth Prompt with AADV2 connection, the token is received but after one hour or so, the bot again prompts for "Signin", from my understanding the bot should fetch the new token silently and not prompt the user to sign in.

In the sample, it mentions about this but not sure what exactly that means.

How do we get the new token without another sign in the prompt as the user is already signed in?

3 Answers

The bot is not designed to automatically refresh tokens. That's on the dev to check that the token is valid/not expired and if so, request a new one (reprompt for signin). The line of code that you've reference in your second link exhibits how the prompt is not sent to the user on every turn (that would get very tedious for users), but instead checks every turn that that the token is valid. For the hour that the token is valid, it returns the valid token and the user is unaffected in anyway. After the token is expired, because this check, the oauthprompt card is resent to the user to log back in.

Broken down, the following method runs if the user chooses 'yes' on a prompt from a previous step. It then takes that activity (const result = stepContext.result), and sends it back to OAuthPrompt to see if there is a valid token on it. :

async displayTokenPhase1(stepContext) {
        await stepContext.context.sendActivity('Thank you.');

        const result = stepContext.result;
        if (result) {
            // Call the prompt again because we need the token. The reasons for this are:
            // 1. If the user is already logged in we do not need to store the token locally in the bot and worry
            // about refreshing it. We can always just call the prompt again to get the token.
            // 2. We never know how long it will take a user to respond. By the time the
            // user responds the token may have expired. The user would then be prompted to login again.
            //
            // There is no reason to store the token locally in the bot because we can always just call
            // the OAuth prompt to get the token or get a new token if needed.
            return await stepContext.beginDialog(OAUTH_PROMPT);
        }
        return await stepContext.endDialog();
    }

Part of the constructor for an OauthPrompt is checking if there is already a valid token:

/**
     * Creates a new OAuthPrompt instance.
     * @param dialogId Unique ID of the dialog within its parent `DialogSet` or `ComponentDialog`.
     * @param settings Settings used to configure the prompt.
     * @param validator (Optional) validator that will be called each time the user responds to the prompt.
     */
    constructor(dialogId: string, private settings: OAuthPromptSettings, private validator?: PromptValidator<TokenResponse>) {
        super(dialogId);
    }

 public async beginDialog(dc: DialogContext, options?: PromptOptions): Promise<DialogTurnResult> {
        // Ensure prompts have input hint set
        const o: Partial<PromptOptions> = {...options};
        if (o.prompt && typeof o.prompt === 'object' && typeof o.prompt.inputHint !== 'string') {
            o.prompt.inputHint = InputHints.ExpectingInput;
        }
        if (o.retryPrompt && typeof o.retryPrompt === 'object' && typeof o.retryPrompt.inputHint !== 'string') {
            o.retryPrompt.inputHint = InputHints.ExpectingInput;
        }

        // Initialize prompt state
        const timeout: number = typeof this.settings.timeout === 'number' ? this.settings.timeout : 54000000;
        const state: OAuthPromptState = dc.activeDialog.state as OAuthPromptState;
        state.state = {};
        state.options = o;
        state.expires = new Date().getTime() + timeout;

        // Attempt to get the users token
        const output: TokenResponse = await this.getUserToken(dc.context);
        if (output !== undefined) {
            // Return token
            return await dc.endDialog(output);
        } else {
            // Prompt user to login
            await this.sendOAuthCardAsync(dc.context, state.options.prompt);

            return Dialog.EndOfTurn;
        }
    }

The comment isn't very clear on this. I've rewritten some of it for clarity, and will reach out to coworkers to fix this.

// Call the prompt again because we need the token. The reasons for this are:
// 1. If the user is already logged in, we do not need to store the token locally in the bot. We can always just call the prompt again to get the token.
// 2. We never know how long it will take a user to respond. By the time the
// user responds the token may have expired. The user would then be prompted to login again.
// 3. If the token is expired, OAuthPrompt will send a new login card to the user to login, and get a new token

I haven't worked with signin all that much, but when I've see this behaviour, it's because I was caching the token myself, and the token you get back is very short lived (1-2 hours). In the end, I realised you're actually meant to call getUserToken every time you want a token - it will cache the token correctly for you (albeit that you need to make a call every time - I wanted to avoid that).

alwaysPrompt flag should be false. Then OAuth prompt will get refresh tokens silently without prompting login card. Please refer the answer on github about this isse.

Bot composer OAuth refresh token

Related