Comments (53)
The initial outline:
Meteor guide: Accounts
Meteor's login system works pretty well out of the box, but there are a few tips and tricks to set things up just the way you want them.
- Picking an accounts UI package
- Setting up password reset, email verification, and enrollment emails
- Setting up OAuth login services
- Building your own login service
- Adding custom fields to the users collection and using them
from guide.
Let's go over this!
Accounts UI package
So accounts-ui
is great for a simple demo or prototype, but isn't really customizable enough for a production app.
For production apps, we'll recommend useraccounts, which seems to be mostly maintained by @splendido. It's possible that we should actually deprecate accounts-ui if useraccounts is easy enough to use. It looks like you can just include a template, although it doesn't come with a dropdown. That seems to indicate you basically need routing in your app to use it, but that doesn't matter for the guide.
Email customization
This should be pretty straightforward, barring some of the bugs people seem to have reported with this system. We should make sure we list all of the things people might reasonably want to do here.
OAuth
If there is a Meteor package for your desired service, it's trivial. If there isn't, it's close to impossible. I should try building a new OAuth login handler and see what I run into; maybe it's not as hard as it sounds using the oauth1
and oauth2
packages.
Custom fields in user collection
I think the right thing to recommend here is making a new collection called UserProfiles
or something. Adding fields to Meteor.users
and messing around with them just seems dangerous, and will certainly not work if we add new fields to the collection, or switch out the database at some point.
Other possible things
- How do you do login in a native app? Like if you're using
meteor-ios
? - What about authenticating static files?
- Should we move a lot of the auth logic from #15 (URLs and routing) here? It might fit better in a discussion of the user system than routing. Or maybe it goes in security. It's a big cross-cutting concern! Either way, we should link there from here.
from guide.
I think the right thing to recommend here is making a new collection called UserProfiles or something. Adding fields to Meteor.users and messing around with them just seems dangerous, and will certainly not work if we add new fields to the collection, or switch out the database at some point.
Oh really? Actually the approach I used was to add a telescope
object to Meteor.users
and then have my custom properties as user.telescope.customProperty1
… Although I realized later that this was a very bad idea because of Meteor's less-than-stellar support for nested fields…
A better way might've been simply prefixing the names, i.e. user.telescope_customProperty1
. Or yes, using a separate collection (although I feel this might make the overall app's logic more complex and not always work with third-party packages, for example if they expect to look for a property on a user object).
Another thing we might want to cover is how to share credentials between multiple Meteor apps?
from guide.
I'm very interested to hear from anyone that has tried the "dual" collection approach (i.e. Users
and UserProfiles
).
I tried it once years ago with a project and it was kind of a pain, but on further reflection it seems like the right approach. I'd love to hear what the challenges are with it.
from guide.
OAuth
If there is a Meteor package for your desired service, it's trivial. If there isn't, it's close to impossible.
Is that true? It took me about an hour to refactor the accounts-google
stuff for our Oauth service. A good part of that time was getting the button graphic to look right for the loginButtons
helper!
But, maybe some boilerplate Oauth1 and Oauth2 code would be useful.
from guide.
Hey guys, I had no time to check this new (awesome!) project and I still have no idea how you're organizing it, but I'm available to help describing how to use useraccounts
packages if needed.
It looks like you can just include a template, although it doesn't come with a dropdown. That seems to indicate you basically need routing in your app to use it, but that doesn't matter for the guide.
@stubailo the atForm
template works changing it's state to show sign-in, register, etc. even without routing packages so I guess it should be easy enough to put it inside a dropdown even if I never tried so far.
In case you'd really like to deprecate accounts-ui
in favour of useraccounts
we could try to add a loginButtons
template and get something similar to the one provided with accounts-ui
as a side note, obvioulsy, I'd be more than happy to see the useraccounts project becoming the official proposed solution for the accounts UI :-)
from guide.
I'm not a big fan of the dual-collection approach when you're using Mongo. On a relational DB, it makes complete sense and is an awesome approach, but Mongo makes things more difficult.
Say I have 10000 users in my system. Each user has a Users
document and a UserProfiles
document. Users
holds their email address and accounts data, UserProfiles
holds their name and other settings.
Imagine I have an admin tools that shows all users in a sortable paginated table and lets me search through them by email, name, favorite color, etc... If all of this data was in Users
, this search is really easy and performant:
Users.find({
$and: [
{"emails.0.address": {$regex: email}},
{"profile.name": {$regex: name}},
{"profile.favoriteColor": {$regex: favoriteColor}}
]
}, {
skip: skip,
limit: limit || 20,
sort: {
...
}
});
But because these pieces of data are broken into two different collections, that search suddenly becomes really difficult...
Are they just searching and sorting on email? Well then I can just find
on the Users
collection.
Are they just searching and sorting on name or favorite color? Well then I can just find
on UserProfiles
.
Are they searching and/or sorting on email _and_ name/favorite color? What do I do here? I'd have to pull down _all of Users
_ that match their email search, do a secondary sort in memory on name/favorite, and then filter the results that don't match their name/favorite color search until I get limit
results (or the other way around). Suddenly, we're pulling 10-20k documents into memory and sorting them. That's no good.
Because Mongo doesn't do joins, to make queries more performant you'd want to denormalize the email address into UserProfiles
, or denormalize name/favorite color into Users
. Following this to its end, why have separate collections at all? Doesn't it make more sense to denormalize all of UserProfiles
into Users
, and not have the separate collection at all?
Honestly, I feel like 1-1 collection mappings in Mongo are a bit of an anti-pattern in most cases. The lack of efficient joins make it really hard to work with them.
I like @SachaG's approach of using nested object or namespaced fields.
My $0.02.
from guide.
from a post in Security by @stubailo:
The thing about profile goes with some musings in the Accounts section about suggesting that people should use a separate UserProfile collection instead of attaching extra properties directly to the user object.
Just to say that useraccounts
packages makes it easy to add extra fields to the user profile object at registration time.
I think suggesting the use different collections to keep users' data and promoting a package wich does exactly the opposite could appear a little illogical
from guide.
could appear a little illogical
Fair enough, but we need to work with what we have. As far as I know, useraccounts
is the best option for the UI right now.
from guide.
makes sense!
let me know if you need some help setting things up for this chapter.
from guide.
@pcorey the idea would be to use the UserProfiles
collection for everything bar login. So you'd definitely denormalize email
(and username
) onto UserProfiles
and then never touch the Meteor.users
collection again.
So I don't think joins would be an issue..?
from guide.
I worked on a solution with a separate Profiles
collection where a user
could have one profile per organization. It worked out quite well with collection-helpers
actually.
I had to abandon it though because of lack of time and the project was never finished so I never got the chance to go back working on that approach.
from guide.
@zimme do you have any data from that attempt that would be helpful here?
from guide.
There's one thing I haven't considered - merging accounts from different oauth providers. How important is this to people? Do many serious apps need this functionality?
from guide.
Yeah, I've done it a few times over the course of things.
I think at least a basic outline of how to do it is useful.
from guide.
@tmeasday do you have a preferred package or approach for it?
from guide.
I'm not sure what packages are out there. Our approach has tended to be something like:
Client side:
Google.requestCredential(options, function(tokenOrError) {
var secret = OAuth._retrieveCredentialSecret(tokenOrError);
Meteor.call('updateAccountWithGoogleService', tokenOrError, secret, cb);
});
Server side:
Meteor.methods({
updateAccountWithGoogleService: function(token, secret) {
var result = Google.retrieveCredential(token, secret);
Meteor.users.update(Meteor.userId(),
{ '$set': { 'services.google': result.serviceData } });
}
})
from guide.
Actually merging two existing accounts is probably way harder and not a can of worms I'd advise getting into (as advice to a dev).
from guide.
Oh, yeah for sure. I mean for new logins.
from guide.
@stubailo, I don't have anything working to show you code wise but what I was doing was.
Setup a Profiles
collection, setup a .profiles()
helper on the user
object with matb33:collection-helpers
.
This app only had enroll signup. e.g. an admin of an organization would invite a user, what would happen then were that a new user was created and on creation time a profile was created for this user and organization was added to Profiles
and an enrollment email was sent out.
When the user logged in he was going to get the option to choose a profile/org after login or by default login to previously chosen profile/org, this part was never implemented.
from guide.
Me, @brettle and @splendido are working together on getting a "sane" way of adding services onto accounts here https://github.com/meteor-useraccounts/one-account/issues and https://github.com/meteor-useraccounts/one-account/wiki/Architecture.
All three of us have packages for solutions of adding services to accounts (mine isn't published) and one solution also tries to merge two existing accounts. Merging two existing accounts is an approach which pretty much all of us have discarded as a proper solution, because it's so hard to keep track of userId
references that needs updating on merge.
What we're trying to do is see if we can't do changes to the accounts-*
packages to facilitate the adding of multiple services to one account.
@tmeasday solution seems like a pretty easy and non-intrusive way of adding a service to an account which only seem to work if one is already logged in.
/cc @stubailo
from guide.
The only reason I wanted to use a separate Profiles
collection was because of support for multiple profiles per user
, i.e. one per organization.
If the app at that time didn't need support for that I would have just used the ´profile` field and populate that with strictly profile related stuff. (The insecure editing of users own profile isn't a good default as new developers misuse that functionality, but that's another discussion).
If I would have ever needed to set some user settings I would have just created a settings
field and used that.
On the other hand if I were to write a package for handling user profiles or user settings. I would give the developer a choice of using the Meteor.users
collection or to specify which collection to use for profiles and which to use for settings.
I'm not too fond of having 1-1 mappings of collections when there's no "extra" need for it, e.g. a Profiles
collection which can just as easily live in profile
on the user object. But having a Settings
collection which can hold user settings and organization settings and other kinds of settings is fine.
edit:
Using Profiles
and denormalizing the username
and email
and/or other info isn't the worst solution either.
from guide.
Me, @brettle and @splendido are working together on getting a "sane" way of adding services onto accounts here https://github.com/meteor-useraccounts/one-account/issues and https://github.com/meteor-useraccounts/one-account/wiki/Architecture.
What's the best way for me to keep track of the progress? Let me know if you need anything to make this a success.
from guide.
I'd say following the issues on https://github.com/meteor-useraccounts/one-account is a good start, but now that I know you're interested I'll try and keep you in the loop too.
One thing that would be very good for us to know is how likely it would be to get a PR through that breaks backwards compatibility with an option to turn on the old behaviour?
Meteor.loginWith<Service>
creates a new account if it does not exist yet. This is something we wanna change, the developer could then just call the Meteor.createUserWith<Service>
method if the login failed to get the old behaviour.
Our current thinking, to keep backwards compatibility, is to add an option {..., createAccount: false, ...}
to have Meteor.loginWith<Service>
not create an account if it didn't find one. But I'd prefer to have the new behaviour as default and specifying createAccount: true
to get the old behaviour or use the above approach.
This would make loginWith<Service>
behave as loginWithPassword
.
from guide.
@zimme let's take this conversation to a different thread, perhaps open an issue on meteor/meteor?
from guide.
Fair enough, we'll probably open a WIP PR or Issue once we have gotten to the point that we're happy with the new API/behaviour.
from guide.
I guess I'm saying an issue would be a great place for us and others to discuss any such API changes or improvements, rather than on this thread.
I'm really excited that you guys are working on this!
from guide.
I see the benefit of that, but I think it will be more productive once we at least have a somewhat comprehensive suggestion =)
edit:
Skipping the free for all suggestions and have people come up with more hands-on alterations
from guide.
Okay, I have created a more complete and detailed outline for this article. See here: #68
from guide.
One thing that occurred to me a little later is -- if we are recommending people use a Profiles
collection to actually work with user data, then does it matter if they have multiple Meteor.users
?
i.e it could be a 1-many mapping from Profiles
-> Meteor.users
.
In fact, I'm pretty sure that was the approach I took on the MentorLoop project years back. @dburles -- is that still the way the codebase works there? What are your thoughts on the approach, having worked with it a lot more than I ever did?
from guide.
That's right @tmeasday Profiles
contained an array of associated userIds.
From my experience the seperate collection makes it a bunch more difficult to manage throughout the application for most cases rather than simply attaching information against the user document, but it comes down to the application.
collection-helpers/transforming the collections can certainly ease references between the two collections. You also have the issue of dealing with joined reactive publications.
I'm not sure I can really recommend one way or another, it would be helpful to illustrate the pitfalls and ways to work with either approach, (is that a cop out?) :)
from guide.
Probably the biggest issue I see with it is the ubiquity of Meteor.userId()
as a pattern. If really you want to refer to the Profile
as the "user", which can have multiple userId
s, then you have a bit of an issue.
In the 1-1 world, you can just set profile._id = user._id
and not think about it.
from guide.
Then again, it would be pretty easy to do something like
Profiles.current = () => {
return Profiles.findOne({userIds: Meteor.userId()});
}
from guide.
I've also thought about the approach with using a separate Profiles
collection and using the profile
as the "user" in the system, this would completely circumvent the problem we're seeing with having multiple accounts services for a user and trying to merge them.
Gonna play around with this approach on Sunday if I'm not too hungover.
edit: Thinking about it, it might not be as easy as that, you still need to choose which profile to connect the new account with.
from guide.
I also think the ubiquity of userId
and user
properties/methods won't be playing well with a separate collection. But might be my personal feeling only...
from guide.
OK, I think all of this discussion has convinced me that having a separate collection for user profile information is not feasible at the moment. I've rolled it back to recommending avoiding profile
and adding top-level fields to the users collection. Please pull-request the outline if you have other ideas and concerns.
Just to say that useraccounts packages makes it easy to add extra fields to the user profile object at registration time.
I'm worried that the profile
field is too tainted at this point by being public by default, plus it doesn't work well with DDP sub-object publications - it's hard to correctly publish only some fields of profile. Would it be possible to enable adding top-level fields to the users collection in your package? Do you think it's a good idea? We could also go with @SachaG's idea of prefixing the fields.
from guide.
I have no problem with using the profile
field, if and only if you use it for things like first/last name, address, age, and other stuff that the user should be able to change them selfs at any time.
But it's too over used by other stuff which is pretty bad.
from guide.
other stuff that the user should be able to change them selfs at any time
The problem I have with this is, I can't think of any world where you wouldn't want to validate that data, which isn't possible if you use profile
.
from guide.
Oh, I guess I didn't think about this stuff since I use simple-schema/collection2/autoform a lot which uses deny rules for validating data which probably override the default allow rule for profile
from guide.
@stubailo so far additional fields have been put under profile
but we might think about suggesting to leverage the onCreateUser
hook to move stuff in a different place.
UserAccounts takes care about additional fields validation (enforced on the server side...), so once the new user will be created with data moved to an appropriate place everything should be good to go.
Makes sense?
from guide.
See #82
from guide.
It would also be awesome if there were a suggested way of changing the name of the underlying mongodb collection, and the collection itself. Although the term "users" is self explanatory, I've come to meet teams who are not comfortable with it while onboarding with meteor and asked for ways to change it. So a "do this if you want that" note would go a long way for those coming from other disciplines.
from guide.
I've come to meet teams who are not comfortable with it while onboarding with meteor and asked for ways to change it.
Can you explain more? Seems like this is a pretty standard term for people who are using your app. Also, do you mean changing one of more of the following:
- The name in the database
Meteor.users
- The schema of the collection
from guide.
I mean 1 & 2. I have no problem with it but had people ask me about it. So I figure, a small note about that in the guide might come in handy.
I did not hear about nor had the need myself to "change" the schema, apart from adding fields, so I can't comment on that. It is fine for me. Although, maybe a note about the use of mandatory ObjectId could be a heads up for people trying to use a common database and accounts collection between a meteor app and a non-meteor app.
from guide.
@robfallows, you said:
Is that true? It took me about an hour to refactor the accounts-google stuff for our Oauth service. A good part of that time was getting the button graphic to look right for the loginButtons helper!
I'm currently in the middle of writing this article, do you have any resources you could point to for some of that boilerplate code? Perhaps there's a blog post or article somewhere that talks about it?
from guide.
Hmm. No. I guess if you add in the time I spent looking for guidance on the web, it went way over the hour! In the end I knew that our Oauth service flows were very similar to Google's Oauth2 flows, so I stood on the shoulders of giants (i.e. I took MDG's code and made a bunch of minor changes). For our use case that was a pragmatic, if somewhat lazy, answer.
However, I'm happy to contribute. As long as the service you're wanting to write a login package for is well documented and you understand the principles of Oauth and how to hook into the flows, it's not hugely difficult. The underlying principles are fairly solid, it's just the variety of implementations which causes headaches.
I have to add that I am somewhat opinionated about Oauth - including what it's supposed to be used for, and why 3rd party service providers are not always a good idea.
from guide.
You know, the more I look back at what I did to implement this, the more I think that we could have a tool to generate the package code for an OAuth2 provider (well, most of the standard stuff anyway). My UI skills are legendary (in their awfulness), but I could have a play around with that and see where it takes me.
I've not looked into OAuth1 stuff at all, so have no comment on that.
from guide.
I'd say the best first step would be to just write down a paragraph or two about the concepts one would need to understand to build an oauth handler. If a tool pops up later we can mention it, but I'd rather have something vague that goes over the concepts rather than some long content that could get outdated fast or have small inaccuracies.
from guide.
I'd say the best first step would be to just write down a paragraph or two about the concepts one would need to understand to build an oauth handler.
Working on that.
from guide.
Help!
I'm struggling with knowing how to stop writing this thing! I covered the basic concepts of OAuth and then started in on a step-by-step guide to implementing an Imgur OAuth handler as a vehicle to demonstrate the concepts. That sounds more like a blog than a "paragraph or two" for the guide.
from guide.
@robfallows that would be a much needed blog post anyway. I'm sure @stubailo could then use the parts he feels fit for the guide as well.
from guide.
Yeah sounds great! I'll happily link to the detailed post.
from guide.
I have posted this.
(Constructive) criticism is appreciated 😄
from guide.
Related Issues (20)
- [hexo] Cannot start local hexo server "unknown block tag: endraw"
- Link to OK Grow article for MongoDB Atlas oplog tailing no longer works HOT 2
- Add Windows getting started guide HOT 1
- Section 6 - Running on Mobile - 404 HOT 2
- Remove or improve the part about crosswalk HOT 3
- Add section about eager loading of files
- Can't use Tailwind CSS v2.0 because postcss@^8.0.9 is not supported by juliancwirko:postcss HOT 1
- Add page last updated date? HOT 1
- angular is not supported HOT 3
- Helmet Example Link is now a 404 HOT 4
- missing meteor test --drive-package information HOT 3
- Update testing section with Cypress HOT 2
- A list of meteor URLs needed to be added for a corporate proxy whitelist HOT 1
- Update Guide to explain 1.7's new lazy loading capabilities HOT 5
- Add testing with Cypress to the guide HOT 5
- Documentation error in the Meteor guide Method section. HOT 1
- Blank screen issue on android mobile with meteor version 1.7 HOT 2
- Improve Vue page HOT 1
- Action Required: Fix Renovate Configuration
- Add to TypeScript section HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from guide.