Search code examples
meteormeteor-accounts

How to populate client-side Meteor.user.services after OAuth with built-in accounts-ui package in Meteor v1.4+?


I'm using accounts-ui and accounts-google in Meteor v1.4.1. I can't get the user.services object to appear scoped in the client code. In particular, I need google's profile picture.

I've configured the server-side code to authenticate with Google like so:

import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';

const services = Meteor.settings.private.oauth;
for (let service of Object.keys(services)) {
  ServiceConfiguration.configurations.upsert({
    service
  }, {
    $set: {
      clientId: services[service].app_id,
      secret: services[service].secret,
      loginStyle: "popup"
    }
  });
}

...and the client side code to configure permissions like so:

Accounts.ui.config({
  requestPermissions: {
    google: ['email', 'profile']
  },
  forceApprovalPrompt: {
    google: true
  },
  passwordSignupFields: 'EMAIL_ONLY'
});

When users click the 'Sign-In with Google' button, a pop-up appears and they can authenticate. No prompt appears, however, despite forceApprovalPrompt being set to true for google.

The big issue is that when I execute this,

const user = Meteor.user();
console.log(user.services);

anywhere in client code, I do not see the expected user services information. I check my database and it is definitely there for the taking:

$ mongo localhost:27017
> db.users.find({})
> ... "services" : { "google" : { "accessToken" : ... } } ...

I'm curious what I'm missing? Should I explicitly define a publish function in order for user services data to exist in the client?


Solution

  • The services property is intentionally hidden on the client side for security reasons. There are a couple of approaches here :

    Suggestions

    1. My preferred one would be to expose a meteor method to bring you the public keys and avatars you might need in the few places you'd need them.
    2. On a successful login, you could record the data you need somewhere in the user object, but outside of the services property.
    3. As you said, you could make a new publication which explicitly specifies which fields to retrieve and which ones to hide. You have to be careful what you publish, though.

    Code Examples

    1. Meteor methods:
    // server
    Meteor.methods({
        getProfilePicture() {
            const services = Meteor.user().services;
            // replace with actual profile picture property
            return services.google && services.google.profilePicture;
        }
    });
    
    // client
    Meteor.call('getProfilePicture', (err, profilePicture) => {
        console.log('profile picture url', profilePicture);
    });
    
    1. Update on successful user creation (you might want to have a login hook as well to reflect any avatar/picture changes in google):
    // Configure what happens with profile data on user creation
    Accounts.onCreateUser((options, user) => {
      if (!('profile' in options)) { options.profile = {}; }
      if (!('providers' in options.profile)) { options.profile.providers = {}; }
      // Define additional specific profile options here
      if (user.services.google) {
        options.profile.providers.google = {
          picture: user.services.google.picture
        }
      }
      user.profile = options.profile;
      return user;
    });
    
    1. Publish only select data...
    // Server
    Meteor.publish('userData', function () {
      if (this.userId) {
        return Meteor.users.find({ _id: this.userId }, {
          fields: { other: 1, things: 1 }
        });
      } else {
        this.ready();
      }
    });
    // Client
    Meteor.subscribe('userData');