Single sign on in Teams application between tabs and the bot - botframework

Using the Bot Framework w/ Microsoft.Bot.Builder v4.6.3
Is it possible to have users sign in only once using the web-based authentication flow, doesn't matter if they sign in via tabs or via bot conversation? If they sign in via a link from a tab, I'd like to have the bot know about this.
I have tried the following for test, omitting any security checks:
All pages are with the following js files imported:
https://statics.teams.microsoft.com/sdk/v1.4.2/js/MicrosoftTeams.min.js
https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.9.1/oidc-client.min.js
On load, the tab page executes microsoftTeams.initialize();
Add a button to the tab page:
<button onclick="authenticate()">Authenticate</button>
The authenticate function contains the following:
function authenticate() {
microsoftTeams.authentication.authenticate({
url: window.location.origin + "/tabs/tabAuthStart",
width: 600,
height: 535,
successCallback: function (result) {
// The debug function just displays what's sent to it using document.write()
debug(result);
},
failureCallback: function (reason) {
debug(reason);
}
});
}
The tabAuthStart page contains the following script which is executed on page load:
microsoftTeams.initialize();
const mgr = new Oidc.UserManager({
userStore: new Oidc.WebStorageStateStore(),
authority: '<my-identity-server>',
client_id: '<my-id-srv-client>',
redirect_uri: window.location.origin + '/tabs/tabAuthCallback',
response_type: 'id_token token',
scope: '<my-requested-scopes>',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true
});
mgr.signinRedirect();
After a successful sign in at the identity provider, I'm redirected back to /tabs/tabAuthCallback
On load, the /tabs/tabAuthCallback executes the following code:
microsoftTeams.initialize();
var mgr = new Oidc.UserManager({ userStore: new Oidc.WebStorageStateStore(), loadUserInfo: true, filterProtocolClaims: true });
mgr.signinRedirectCallback().then(function (user) {
// I expected something involving a bot to happen after calling this
microsoftTeams.authentication.notifySuccess({
idToken: user.id_token,
accessToken: user.access_token,
tokenType: user.token_type,
expiresIn: user.expires_at
})
}).catch(function (err) {
microsoftTeams.authentication.notifyFailure("UnexpectedFailure: " + err);
});
The pop-up window is closed and the successCallback function from the tab is executed successfully with the user information that I have sent. However, the bot is not in any way notified about this (as far as I know). I have set a breakpoint in the bot controller action resolved by POST /api/messages but it's never hit.
Do I need to handle this manually? I.e. pass the user info to the back-end? But even if so, how do I know which Teams user to associate this user info (i.e. access token) to?
If this is possible to do in a reliable and secure way, would it also be possible in the opposite direction, i.e. having the user token available to the tab if they have already been authenticated from a bot conversation or a messaging extension? Is there a reliable way to identify a Teams user who's navigating tabs, in order to obtain their access token from the back-end, assuming the back-end already obtained them via the authentication mechanism?

Related

Pass variables between different server-less functions

I am building an application using the Twitter API and Netlify (aws lambda functions)
This API requires these steps:
When the user goes to my /auth function, a link to the Twitter authentication is created
Once the user clicks that link, he is redirected to Twitter where a pop-up asks to allow my app to connect.
Once the user approves, he is redirected to my /auth function again but this time the authCode is set to a number rather than being undefined. This authCode is used to instantiate the twitter client class and authorize it.
A new instance of the Twitter client is created and authorized. This instance allows to query the tweets
1, 2 and 3 works. However, the authorized instance only lives inside the /auth function. How can I pass it to different functions without losing its instantiation?
How can I pass this instance to different server-less functions?
client = new Client(OAuthClient) this is what I want to pass around.
I tried with a Middleware with little success. It seems the twitter client class gets re-instantiated (so without authorization) for every server-less function
https://playful-salmiakki-67d50e.netlify.app/.netlify/functions/auth
import Client from 'twitter-api-sdk';
let client: Client;
const auth = async (event, context, callback) => {
const authCode = event.queryStringParameters ? event.queryStringParameters.code : undefined;
const authUrl = OAuthClient.generateAuthURL({
state: 'STATE',
code_challenge: 'challenge',
});
console.log('HERE LINK:');
console.log(authUrl);
if (authCode) {
await OAuthClient.requestAccessToken(authCode as string);
client = new Client(OAuthClient); <-- THIS IS WHAT I WANT TO PASS TO DIFFERENT FUNCTIONS
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'Auth, go to the url displayed terminal'}),
myClient: client
};
};
exports.handler = middy().use(myMiddleware()).handler(auth);

Sending a JWT through Direct-line API to authenticate the user, in Microsoft chat bot

I need to send a JWt (access token) to the chatbot via directline. I'm using react as the front end, and the chatbot is integrated into the front end via botframework-webchat.
So far, I was able to send the access token through an activity, which is not recommended as I think.
Right now, the chatbot is asking the user to log in, which is not good because the user is already logged in to the application.
My first question - Is it possible to authenticate the chatbot by an id token instead of connecting with Azure AD, B2C, or any auth service provider?
If it is possible, How can I send the id token to the bot, via botframework-webchat
Thanks in advance
Here is my code for the front end
const Chatbot = (props) => {
const language = localStorage.getItem('language');
const directLine = useMemo(
() => createDirectLine({ token: <my_token>, locale: 'sv-se' }),
[]
);
useEffect(() => {
var activity = {
from: {
id: '001',
name: 'noviral',
},
name: 'startConversation',
type: 'event',
value: 'Hi noviral!',
locale: language === 'en' ? 'en-US' : 'sv-se',
};
directLine.postActivity(activity).subscribe(function (id) {
if (console) {
console.log('welcome message sent to health bot');
}
});
}, []);
return (
<Layout className="login-layout">
<div className="login-div">
<div className="chatbot">
<div className="consent-wrapper">
<ReactWebChat
directLine={directLine}
userID={'001'}
username="Noviral"
locale={language === 'en' ? 'en-US' : 'sv-se'}
></ReactWebChat>
</div>
</div>
</div>
</Layout>
);
};
export default withTranslation()(Chatbot);
Sending the token via an activity is acceptable as activities sent via Direct Line are secure. If you look over the 24.bot-authentication-msgraph sample, you can see that the default action the bot takes is to send an activity displaying the user's token.
As for authentication, the question doesn't seem to be what token you will use but rather how you will authenticate. If you don't use a service provider + login, how is the bot going to verify who the user is? That being said, there are some SSO (single sign-on) options available via Web Chat (see here) that, if a user is already logged in, then SSO could pick it up. You will have to look them over to decide if these options meet your needs.

Sign up with email verification does not create a session token when user get's redirected to web app after clicking the verification link

According to Parse js guide:
The Parse.User obtained from Parse.User.current() will always be authenticated. If you need to check if a Parse.User is authenticated,
you can invoke the authenticated method. You do not need to check
authenticated with Parse.User objects that are obtained via an
authenticated method.
So when a user Signs up on my web app, get's a verification email which after clicking will direct the user back to the app. Then I call Parse.User.current() which returns the user object but the sessionToken it's empty. The user object looks like this:
ACL: {*: {…}, vIl61voP42: {…}}
createdAt: "2019-11-08T11:47:44.102Z"
emailVerified: true
objectId: "vIl61voP42"
origin: "Web App"
sessionToken: undefined
updatedAt: "2019-11-08T11:50:19.633Z"
username: "dfgg#dfgg.com"
My goal would be to allow the user to access the app without having to manually login and therefor create a session. From Parse docs my understanding was that once we have a user returned from Parse.User.current() would mean this user would be logged in and therefor I could set and save new data into the user object, which is not the case...
For sign up I've a standard Parse sign up as per documentation:
var user = new Parse.User();
user.set("username", "my name");
user.set("password", "my pass");
user.set("email", "email#example.com");
try {
await user.signUp();
// Hooray! Let them use the app now.
} catch (error) {
// Show the error message somewhere and let the user try again.
alert("Error: " + error.code + " " + error.message);
}
Once sign up form it's submitted the user receives a email confirmation link which will redirect them to my application url. This is how my Parse server settings looks like:
const api = new ParseServer ({
...
verifyUserEmails: true,
emailVerifyTokenValidityDuration: 0,
preventLoginWithUnverifiedEmail: true,
customPages: {
// App
passwordResetSuccess: settings["appUrl"] + "/login",
verifyEmailSuccess: settings["appUrl"] + "/dashboard",
linkSendSuccess: settings["appUrl"] + "/email-confirmation",
// Parse
invalidLink: settings["serverUrl"] + "/expired",
choosePassword: settings["serverUrl"] + "/choose_password"
}
...
})
Finally, once the user arrives at my dashboard route from the verification email link I get the current user via await Parse.User.current() which returns me the user object without a session token. Also checked on my local dev db and there's no session being created.
Any idea?

Parse.com cloud code check user before allowing login

I am using the following code to allow login from my iOS and Android apps. One of our paying customers has hundreds of users (who all use the same domain in their email i.e #mycompany.com) who need to be stopped from using the app immediately.
I cannot re-publish the client app as it takes time for review etc. How can I prevent login from a certain group of users from cloud code.
Parse.User.logIn(user.username.toLowerCase(), user.password, {
success: function(user) {
user.set("platform", ionic.Platform.device());
user.set("os", ionic.Platform.platform());
user.save(null, {
success: function(user) {
console.log('ok');
},
error: function(err) {
console.log('error setting device');
}
});
Is there is beforeSave on the session class?

Google Sign-In gives error when swiching to secondary Youtube accounts

I am currently trying to use gapi.auth2 from Google Sign-In for Websites API and this is the code I have:
-- load the library with:
<script src="https://apis.google.com/js/platform.js?onload=onLoadGapiCallback" async defer></script>
-- initialize an auth2 variable:
var auth2;
window.onLoadGapiCallback = () => {
gapi.load('auth2', () => {
auth2 = gapi.auth2.init({
'client_id': 'CLIENT_ID',
'scope': 'profile email https://www.googleapis.com/auth/youtube.readonly'
});
});
};
-- and when a botton is clicked do:
auth2.signIn().then(() => {
console.log('auth is:', auth2.currentUser.get().getAuthResponse().access_token);
});
This works well, it initializes the auth2 variable, when I click the button, it shows the SingIn prompt and I choose one of my Google Accounts. The problem is from now on when I have to choose a YouTube account, if I choose other account than the main one, I'll get an Exception Object like this one:
{type: "tokenFailed", idpId: "google", error: "USER_LOGGED_OUT"}
also there's an XHR request being sent lastly that has this response:
{"error":"USER_LOGGED_OUT","detail":"No active session found."}
So it only works if I choose the main account, but I cannot choose other YouTube accounts.
What am I missing here?
I've looked into all these docs but none helped me:
Getting profile information
Google Sign-In JavaScript client reference
Monitoring the user's session state
Update:
Running the code from this example (but with this scope: 'profile email https://www.googleapis.com/auth/youtube.readonly') will only work if I choose the first Youtube account for each Google account. If I choose any other Youtube account, I'll get this alert error:

Resources