Authentication with external service

Hello,
We plan to connect our app with Azure (OAuth). I know that there is some packages to do so but I would like to understand the underlying concept.

How to intercept all client-server call and get the user token for subscriptions and for method call ?
I have tried with :

WebApp.rawConnectHandlers.use(

but Meteor.connection is undefined
I can still see the header x_mtok but how to know wihich user is behind ?

And this is not called for Meteor.call and subscription

Thanks a lot for your help

Check out the existing OAuth packages. There is a special landing page that collects the data and sends them to the server. That is what the core OAuth package handles. You then register your provider which then processes the data and does any additional data retrieval.

Thanks but in those packages I cannot understand where the interception of all DDP traffic is done to verify the token. Does Meteor do this under the hood just because we registred a login service ?

Anytime a user do a Meteor.call or subscribe to something how do we retrive this.userId ?

Is there a way to hook into all subscribtion and all meteor call to do this ? or is this a non sense ?

This code seems to give some info :

 // Intercept all DDP messages
  Meteor.server.stream_server.server.addListener('connection', function (socket) {
    const old = socket._events.data
    socket._events.data = function () {
      console.log('Meteor.server.stream_server.server > this._meteorSession : ', this._meteorSession);
      const userId = this._meteorSession?.userId;
      console.log('Meteor.server.stream_server.server > userId : ', userId);
      console.log('Meteor.server.stream_server.server > arguments : ', arguments); 
      /*
      Some examples : 
      [Arguments] {
          '0': '{"msg":"method","id":"122","method":"/task/progress/set/async","params":["TK-2104",100]}'
      }
      
      [Arguments] { '0': '{"msg":"unsub","id":"ofu9SWi6ZYojKGCXo"}' }

      [Arguments] { '0': '{"msg":"pong"}' }
      [Arguments] { '0': '{"msg":"ping"}' }
      [Arguments] {'0': '{"msg":"connect","version":"1","support":["1","pre2","pre1"]}'}
      [Arguments] {
        '0': '{"msg":"sub","id":"iZRqvC4go4eMhmsxS","name":"meteor.loginServiceConfiguration","params":[]}'
      }
      [Arguments] {
        '0': '{"msg":"sub","id":"GrWJQCyZJK6c4uQD5","name":"smarttags","params":[]}'
      }
      */
      old.apply(this, arguments)
    }
  })
}

I think you are mixing here a few concepts that should not be mixed. DDP traffic is a different story than handling OAuth response. DDP and accounts manage the current user id info for you. Similar to how it is handled if you use Apollo GraphQL.
Using OAuth.registerService is a middleware to handle the response from the service after you call OAuth.launchLogin. In that function you do what ever you need to do (often retrieving additional identity information from the service) and then return an object with service data and options:

{
    serviceData: {
      id: identity.sub,
      authCode: query.code,
      accessToken: response.access_token,
      expires: response.expires_in,
      refreshToken: response.refresh_token,
      scope: response.scope,
      tokenType: response.token_type
    },
    options: { profile: { name: identity.name, avatar: identity.picture } }
  }

This is then added to the user serviceData being the details needed to do stuff like refreshes and logins in the future and options attached to the user itself, so you can change there many things, though often you just put in things for the profile field and add a hook Accounts.onCreateUser in case of user creation that plays with those data when creating a user to populate fields like username.

This is how we do custom OAuth in Meteor

  1. Initiate authentication from client for user A
  2. The client uses a method to generate the 3rd party authorization URL from the server
  3. The client saves data in localstorage (if needed) and redirects the user to the 3rd party authorization URL
  4. The redirect URL is the same Meteor app which collects the authorization data from the URL (and any saved data from localstorage) and sends the authorization data through methods (this is what @storyteller mentioned above about the landing page that collects data)
  5. The server method validates the authorization data with the 3rd party and checks if the service ID exists in any user account.
    6a. If the user account does not exist, registration is started in the client
    6b. If the user exists, a login method is started in the client using our generic one-time link login generated from #5

We never use webapp for all our custom OAuth processes.

1 Like

Thanks for all this precisions.
My question is when a user is logged in using an external provider (idp).

Then for all the time of the session (as long as the user does not log out or until the connexion expires), the external identity provider is not called anymore. Am I right ?

For example if the user is revoked inside the external provider (let’s say Azure) then our application will not see it immediately but only when the user will try to reconnect. Right ?

Maybe as you said I am mixing wrong concept but that is why I was thinking of checking again some Meteor call or subscriptions using this Meteor.server.stream_server.server.addListener('connection' ... to double check during the session that the user is still authorised to do it.

Thanks again

Yes, but if you want you can make regular calls to the service or implement a webhook to which the provider will send if the permission is revoked. The second options is the one that is recommended. Additionally if you have some restricted actions that you need to check with the service you should make that call in the method that is being called.

OAuth is meant to validate a session. Therefore, you must “trust” the validation that will last the entire session. What you control in this case is the length of the session. Depending on your needs, you need to adjust your definition of a “session” and let the user re-authenticate once that session expires

2 Likes