Shared login across multiple apps

I’m trying to make a seamless login experience across two Meteor apps, both subdomains of the same domain.

Scenario:

  1. User is logged in to a Meteor app on my-fav-cats.example.com, App1 from now on, which displays a list of cats the user likes
  2. User goes to browse-cats.example.com, App2 from now on, which is another Meteor app on the same domain, where the user can browse some cats and favorite them
  3. When an authenticated user moves from App1 to App2, the user should automatically be logged in there as well, allowing to immediately favorite a few cats, which then would get listed on App1.

Both apps can, if required, access the same database directly.

How can I achieve this with Meteor?

My idea 1:

Use kadira’s login-state package to fake it, so that the App2 gets a cookie with userId, name, avatar and a secret token persisted by App1. The client then connects to App1 and calls a method with the faved cat, userId and secret token, which is used to validate the action.

My idea 2:

Just persist Meteor’s loginToken in the cookie and use that to make actual authenticated DDP method calls from App2 to App1. Probably vulnerable to all sorts of attacks?!

Any more ideas?

Is there any particular reason you want to use subdomains - they do make life much harder?

Why not use www.example.com/my-fav-cats and www.example.com/browse-cats instead?

It’s relatively easy - you can generate a login token easily enough, then when a link is rendered for app1 -> app2 you append a token token=..., then in app2, you check to see if the token is present and call Meteor.loginWithToken - how often will users bounce back and forth? Logging in every time could be arduous for a user, depending on how much content requires the uesr

1 Like

I’m using this method:
User logged in app1, then switch to app2.
In app2, we make a HTTP.call to app1 from the client browser. The app1 server responses data with a kind of token.
After received this token, app2 browser call the Accounts.callLoginMethod with this token as methodArguments.
In the app2 server, we check this token (we actually make a server to server call to app1 server to make a double check) then login the current account if token was valid.

Can you elaborate what the payload of request from app2 to app1 is? How does app1 server know which user this is, and furthermore, how does app1 make sure it’s not spewing loginTokens insecurely to anyone asking for one?

This is a business decision :man_shrugging:

I forgot one thing. Our app1 is not a Meteor app. It’s a PHP website. When you make a HTTP.call from browser, it will include the cookies automatically.

The issue for us is (unless I’m not thinking this straight), that as the user has authenticated on app1, Meteor has saved a login token to local storage.

This token, however, is not available in app2 due to obvious browser restrictions.

I could overcome this, possibly, by saving that login token as a domain-wide cookie. Someone might say that this is bad.

Another option I was thinking, was to use an authorization token, generated solely for authorizing these few specific calls from app2 to app1. In this way, even if the token was somehow compromised, the attacker wouldn’t get access to that much really ( only favoriting cats :smiley: ).

I’m now wondering, if there are any more options. The idea @znewsham suggested was brilliant in simplicity, but works only when moving from app1 to app2. I’d like the login state to be persisted, so that the user remains logged in on both apps, even after refreshing the page or hitting app2 directly.

1 Like

I’d like the login state to be persisted, so that the user remains logged in on both apps, even after refreshing the page or hitting app2 directly.

Then I think there is only one thing we can use: cookies

Here’s some relevant rambling in the form of a Meteor blog post: https://blog.meteor.com/why-meteor-doesnt-use-session-cookies-e988544f52c9

My approach allows for persistent login.

In app1, let’s say you have a link <a class="external" href="https://app2.domain.com/whatever">. You add an event handler to that link:

...
events: {
   "click a.external"(e) {
      e.preventDefault();
      Meteor.call("generateLoginToken", (err, res) => {
          //error handling
          window.location = e.currentTarget.href + "?token=" + res;
      });
   }
}
...

In app2.js, you look for the presence of that token in the URL, if it is present, you login the user with it.

if (FlowRouter.current().queryParams.token) {
   Meteor.loginWithToken(FlowRouter.current().queryParams.token);
}

Now the user is logged into both app1 and app2 and can navigate freely between them, or away from them.

If you don’t want the hasle of logging in with every navigation between apps (will depend on how often this happens), you could generate that login token once when the client logs into app1, and cache it in the window, then open the requested URL in a new tab. You could even make the generateLoginToken function check whether the user is already logged into app2, and not return a token in that case.

Obviously this approach is bi-directional too. If your app2 wants to send users to app1 (and log them in) you can use the same approach.

If you REALLY need to be able to hit app2 directly, having logged in to app1, but NEVER having used a link from app1 -> app2, then as @minhna suggested, cookies are really your only option

1 Like

This seems …interesting: Using iframes to “fish” out the loginToken across different domains (also seems very fishy)

Hey @arggh I have created over the last two months a custom Accounts login which involves a custom oauth sever and single account for logging in on multiple apps.

This involves the following packages:

I have basically forked the rocket chat oauth server package and updated it to the latest node oauth, then removed express and rewrote all routes using webapp. It currently implements the authentication code flow which is used by the accounts handlers.

To login with a custom service (currently named as our project but you can fork and customize the names) there are all account packages:

Which also includes

Now you only need to have the account being present on a Meteor server that uses the oauth2-server package and implements the routes.

Your client apps need to be registered as clients (as implicit first step before the oauth flow starts) and have the accounts-lea package be installed.

Now this app can login using the oauth server. No need to change accounts between apps.

Bonus I have implemented this also for ddp login for remote connections:

I am currently not at home so I can add some further info later. I have an example repo but it needs to be updated:

I hope to announce the full system here on the forum once I got all documented.

4 Likes

This is some great stuff. Thanks for sharing and looking forward to the announcement!

Hi @jkuester
I’m trying to get this working.
My oauth server is working fine thanks to leaonline/oauth2-server
But I can’t login my users using it from another Meteor app with your client packages.

the handleOauthRequest in registerService is returning this :

{
serviceData: {
     id: 'k2e8tjEP2ZtTHAqFk',
     accessToken: '6e1943c1058dc1bdd1ef56c784b05c932da574ebd02bf1b588579ff6255c7288',
     email: 'contact@social-boulder.com',
     username: 'SocialBoulder'
   },
   options: { profile: { name: 'SocialBoulder' } }

The popup is closed but the user is not logged in.

Can you help me ? I need this to work today or tomorrow :sob:

@jadus I can see what I can do :slight_smile: can you please open an issue in the leaonline/oauth2-server so we can discuss things there and don’t pollute this topic with debugging code etc.

Edit: nevermind found your issue already at leaonline/meteor-accounts-oauth-lea

@jkuester thanks !!!