I am trying to login to single user with multi OAuth (facebook, google) login service. Here is what I try.
In Client:
'click #signInByFacebook': function (e) {
e.preventDefault();
Meteor.loginWithFacebook({requestPermissions: ['public_profile', 'email', 'user_about_me', 'user_photos']}, function (err, res) {
if (err) {
showError($('.alert'), err, 'login');
return;
}
showSuccess($('.alert'), 'login');
Session.set('notAdmin', !Roles.userIsInRole(Meteor.user(), ["admin"]));
Router.go('/');
});
},
'click #signInByGoogle': function (e) {
e.preventDefault();
Meteor.loginWithGoogle({requestPermissions: ['profile', 'email', 'openid']}, function (err, res) {
if (err) {
showError($('.alert'), err, 'login');
return;
}
showSuccess($('.alert'), 'login');
Session.set('notAdmin', !Roles.userIsInRole(Meteor.user(), ["admin"]));
Router.go('/');
});
}
In Server:
Accounts.onCreateUser(function (options, user) {
if (options.profile) {
user.profile = options.profile;
}
var sameuser = Meteor.users.findOne({$or: [{'emails.address': getEmail(user)}, {'services.facebook.email': getEmail(user)}, {'services.google.email': getEmail(user)}]});
console.log(sameuser);
if (sameuser) {
if (user.services.facebook) {
console.log("facebook");
Meteor.users.update({_id: sameuser._id}, {$set: {'services.facebook': user.services.facebook}});
}
if (user.services.google) {
console.log("google");
Meteor.users.update({_id: sameuser._id}, {$set: {'services.google': user.services.google}});
}
return;
}
console.log('register success');
return user;
});
This code will check if any user logined with facebook/google has the
same email or not with current sign in. If they are the same, just
update information to old account. If not, create new user.
This works great, but there is a problem with the 'return ;' in server code. I dont know what should I return to stop create user and auto login to the user that has same email. Anybody can help this issue ? Thank you.
The only way to stop creation of the new user is to throw an exception, but that will also prevent logging in as the existing user.
However, your general approach is insecure. Consider a user who has a Google account with a strong password and a Facebook account with a weak one. When he uses the Google account to authenticate with your app, he doesn't (and shouldn't) expect that someone who gains access to his Facebook account will be able access your app as him.
A better approach is to require that the user be logged into both services simultaneously before merging the services. The good news is that this also means that you don't need to worry about logging in after preventing the creation of the new user, because the user will already be logged in. Something like this might work:
Accounts.onCreateUser(function (options, user) {
if (options.profile) {
user.profile = options.profile;
}
var currentUser = Meteor.user();
console.log(currentUser);
if (currentUser) {
if (user.services.facebook) {
console.log("facebook");
Meteor.users.update({_id: currentUser._id}, {$set: {'services.facebook': user.services.facebook}});
}
if (user.services.google) {
console.log("google");
Meteor.users.update({_id: currentUser._id}, {$set: {'services.google': user.services.google}});
}
throw new Meteor.Error(Accounts.LoginCancelledError.numericError, "Service added to existing user (or something similar)");;
}
console.log('register success');
return user;
});
There are still a couple loose ends. First, I think Meteor expects OAuth credentials to be "pinned" to the user that they are associated with, so you probably need to repin the credentials you are copying.
Second, the above approach bypasses the validateLoginAttempt() callbacks. If you, or any package you are using, has registered any such callbacks, they won't be called when logging in using the second service, so they won't be able to prevent any such logins that they might consider invalid.
You can address both of these issues and skip the onCreateUser() callback as well, by just adding my brettle:accounts-add-service package to your app.
Related
In the Single Sign-On for Teams
I have the call microsoftTeams.authentication.getAuthToken(authTokenRequest); working; that is, it successfully returns a token resolving to my Azure Active Directory (AAD) successfully. All good. Surprisingly easy. JWT returns with correct audience and scopes (as I have set in my tenant's AAD)
However what I get back when I decode the JWT this seems to just be an Authentication Token, not an Access Token.
Looking at the sample at Task Meow/teams.auth.service.js Does not seem to show how to swap the Auth for the Access Token.
I assume the code will look something like the method getToken() ... but since I have already spent 10+ working days on auth (old ADAL OH MY GOODNESS WAS THIS HORRIBLE) ...
Question:
I was wondering if there are any other good samples of MicrosoftTeams.js Authenticate / Auth Token / MSAL Access token out there?
Anyway, I did solve my problem by the following
Follow TaskMeow example through the abstractions ofauth.service.js > sso.auth.service.js > teams.auth.service.js
As I wanted additional AAD scopes (Files.ReadWrite.All to access the Sharepoint Online files in Teams and Groups.ReadWrite.All - to add Tabs) my getToken() method in teams.auth.service.js is something like the following:
getToken() {
if (!this.getTokenPromise) {
this.getTokenPromise = new Promise((resolve, reject) => {
this.ensureLoginHint().then(() => {
this.authContext.acquireToken(
'https://graph.microsoft.com',
(reason, token, error) => {
if (!error) {
resolve(token);
} else {
reject({ error, reason });
}
}
);
});
});
}
return this.getTokenPromise;
}
Editorial Comment:
Authentication in Microsoft Teams is too difficult
There seems to be many "approaches" in the documentation
The present "SSO" flow still has flaws, and is in "Developer Preview"
If you are an SPA developer it is just too difficult. I am (obviously) not an expert on Authentication -- so current "recipes" are imperative.
This is especially the case if you want more than the default "scopes" as described in Single Sign-on ... and most of the "good stuff" in Microsoft Graph is outside of these default scopes.
Also, this snippet may help.
If you follow the recommended Taskmeow in your Microsoft Teams app, you will get a quick appearance of the Redirect URI (aka /tab/silent-start)
To solve this, adal.js caches the user and access token.
So you can add a check in login()
login() {
if (!this.loginPromise) {
this.loginPromise = new Promise((resolve, reject) => {
this.ensureLoginHint().then(() => {
// Start the login flow
let cachedUser = this.authContext.getCachedUser();
let currentIdToken = this.authContext.getCachedToken(this.applicationConfig.clientId);
if (cachedUser && currentIdToken) {
resolve(this.getUser());
} else {
microsoftTeams.authentication.authenticate({
url: `${window.location.origin}/silent-start.html`,
width: 600,
height: 535,
successCallback: result => {
resolve(this.getUser());
},
failureCallback: reason => {
reject(reason);
}
});
}
});
});
}
return this.loginPromise;
}
I have authClient.js file as below:
export default (type, params) => {
if (type === AUTH_LOGIN) {
return UserLoginService.signIn(params)
}
if (type === AUTH_LOGOUT) {
// ...
}
if (type === AUTH_ERROR) {
// ...
}
if (type === AUTH_CHECK) {
return localStorage.getItem('token') ? Promise.resolve() : Promise.reject({ redirectTo: '/no-access' });
}
return Promise.reject('Unkown method');};
In authentication services, after user signed up, I create temporary password and send it to user via email. I require user to change their password in the first login.
In UserLoginService.signIn(), if it's the first login, I do this reject({ code: 'NewPasswordChallenge' });
Is there any way to redirect to /change-password page if type is AUTH_LOGIN? like when type is AUTH_CHECK?
Not easily for now. You might be able to address this by implementing a custom saga which would be triggered on the USER_LOGIN_FAILURE action. It won't prevent the notification display though.
However, this could be a nice addition I think. You're welcome to make a PR about it :)
My login form currently posts to itself on /
The post request is then picked up as follows...
app.post('/', userController.doLogin);
Then the controller is a follows..
exports.doLogin= function(req, res) {
Parse.User.logIn(req.body.username, req.body.password, {
success: function(user) {
console.log('login success');
res.render('loginSuccess');
},
error: function(user, error) {
console.log('login failed');
res.render('loginFailed');
}
});
}
This works correctly for a correct / incorrect login.
However, once logged in, the session is not stored no cookies are created / local storage etc..
Therefore when I test for login on one of my other routes it always displays as no-session, i am checking with the following code..
if(Parse.User.current()){
console.log('logged in and redirected');
res.redirect('/member/home');
}else{
console.log('not logged in, redirected to home/login page');
res.redirect('/');
}
Which always goes too home / again.
I read here https://parse.com/docs/hosting_guide#webapp-users that...
You just need to call Parse.User.logIn() in Cloud Code, and this middleware will automatically manage the user session for you.
Which would suggest it does the session for me?
Any help would be super useful! many thanks in advance!
Ok so after a lot of digging I have worked it out. First you need to add the module by doing the following..
var parseExpressCookieSession = require('parse-express-cookie-session');
Second you need to setup your own variables like this
app.use(express.cookieParser('YOUR_SIGNING_SECRET'));
app.use(parseExpressCookieSession({ cookie: { maxAge: 3600000 } }));
Third, you must do send the login/session all over HTTPS.
Boom, working - easy peasy when you know how.
When using Yammer SDK and using yam.platform.login method, I don't get any callback when authentication fails or when the user closes dialog window. Is this a bug or something you have seen in your Yammer integration tasks?
My code
yam.platform.getLoginStatus(function (response) {
if (response.authResponse) {
}
else {
yam.platform.login(function (response) {
if (response.authResponse) {
console.dir(response);
}
else {
### CODE NEVER EXECUTED IF LOGIN FAILS OR USER CLOSE POPUP###
}
});
}
});
Make sure to add your web application url to "Javascript Origins" of your registered yammer app.
Make sure you added your web app url to "Trusted Sites" and other Yammer urls.
We get this problem (no callback on yam.platform.login) when the user is currently logged into a network other than the home network (network where app is registered). If your users use multiple networks, you may need to add your app to the global app register.
An alternative (hacky) way is to 'try' the approach below. This worked for us as it only needed to happen once (to get the auth token).
yam.getLoginStatus(function(resp){
if (resp.authResponse) {
//success
} else {
// not logged in
var yamLoginSuccess=0;
try {
yam.platform.login( function (response) { //prompt login
console.log('no response here if user in another network');
if (response.authResponse) {
//success
yamLoginSuccess=1;
}
});
}
catch(err) {
// does not throw an error so this bit is not helpful
}
finally{
if(yamLoginSuccess===0){
alert('Need to be logged into the home yammer first :-/ /n '
+ 'Redirecting now, hit back to come back');
window.location='https://www.yammer.com/YOURNETWORK/';
}
}
}
});
I'm building a phonegap application which will have nodejs at the server side. I wanted to implement login using passport-facebook strategy but their callbacks specify two routes, /successcallback and /failurecallback. Having a single page application, this makes it very confusing to have users redirected to so and so page.
I don't want to serve static files (index.html, login.html) from the server but rather have them on the client and ask the client to make ajax calls. So far, I'm able to make /auth/facebook call as an AJAX request but I can't receive any response on the same request because the login strategy requires the user to be redirected. I'd rather want to send a user_id or name back to the user on successful login or show him the login form (which is also on the www directory in phonegap) on failure. But the redirection and CORS errors are preventing me from doing this. Is there any way I can implement this? I've looked for this since a few weeks now, but no success. I'd really appreciate your help!
PS: I'd rather avoid having to send all html and static content from the node server.
EDIT: Adding login code for better understanding:
app.get('/userpage', utility.isLoggedIn, function(req, res)
{
res.send('User:'+req.user);
});
app.get('/', utility.isLoggedIn, function(req, res)
{
res.redirect('/userpage');
});
app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback',passport.authenticate('facebook',
{
successRedirect : '/',
failureRedirect : '/login'
}));
app.get('/logout', function(req, res)
{
req.logout();
res.redirect('/login');
});
utility.isLoggedIn:
function isLoggedIn(req, res, next)
{
if (req.isAuthenticated())
return next();
res.redirect('/login');
}
You can't do that with facebook oAuth, but Facebook provides another login solution where you can code your client app to request a token, that you can later validate on the server with passport-facebook-token.
This way you can use the advantages of passport for persistent sessions, without that annoying redirection.
Instead of using the standard redirections offered by passport, you can define your own function which will be executed instead of the redirection. Here's an example of what that code would look like
passport.authenticate('login', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.json({ status:"failed", "error": "Invalid credentials" }); }
// req / res held in closure
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.json({ "status":"success"});
})
})(req, res, next);