
It will be a question of the undervalued
feathersjs framework.
In a nutshell, how it works you can read
here . One working morning, I received a new TZ in the messenger, and it was described as follows: you need to separate users for 2 services (that is, that back would process authentication requests from two different fronts).
That is, we have: 2 separated fronts written in VueJs located on different domain names, a common backend written on feathers (and of course one users table in the role database).
That is, the table can contain 2 of these fields
')
email pass project +
Naturally password is hashed.
As a module to enter the service, we use
feathers-authentication (
passportjs version adapted for feathers).
And so, as for the local login, everything is simple. In order to determine where a request with a login / password pair came to the back, you can insert another parameter on the front of the request body, for example “project”, and search for this parameter in the database of the desired user.
A little more about how to make a local auth user. I created a separate file for authentication (it was the file, not the service generated) auth.js, connected it to app.js. So auth.js will look about you
const authentication = require('feathers-authentication'); const jwt = require('feathers-authentication-jwt'); const local = require('feathers-authentication-local'); const oauth2 = require('feathers-authentication-oauth2'); const FacebookStrategy = require('passport-facebook'); const commonHooks = require('feathers-hooks-common'); module.exports = function () { const app = this; const config = app.get('authentication'); app.configure(authentication(config)); app.configure(jwt()); app.configure(local(config.local)); app.service('authentication').hooks({ before: { create: [ commonHooks.lowerCase('email'), authentication.hooks.authenticate(config.strategies) ], remove: [ authentication.hooks.authenticate('jwt') ] } }); };
In general, now there is nothing interesting in it, the only thing I would like to note is that I did not put hooks in separate files (as it happens when you call the standard generate service), and added a hook commonHooks.lowerCase ('email') - I think it is understandable for what does he need.
And now let's add some magic. Having rummaged in documentation I found a
class verifier which can be expanded and add the f-tional. I added a new configuration for local auth to the anonymous function.
app.configure(local({ Verifier: CustomVerifier }));
and summoned my new class
class CustomVerifier extends Verifier { verify(req, username, password, done) { return this.app.service('users').find({ query: { email: username, roles: req.query.project } }).then(res => { const user = res.data[0]; if (user) { const userId = res.data[0].id; this._comparePassword(user, password).then(() => { if (!user.isVerified) { done(null) } else { done(null, user, { userId: userId }); } }).catch(err => { done(null) }) } else { done(null) } }) } }
What does this class do? First we connect to the users service - this.app.service ('users') - and call the find method with the query parameter. That is, we are looking in the DB for the user we need by two fields, and if we find it, the response (variable res) will contain the array of found users, if no users are found, then the array will return empty. Then we call the function
this._comparePassword()
where we send as a parameter the found user and the password that came from the front. The _comparePassword function hashes the password and compares it with the password that lies in the database and if the password matches, then we call it in then ()
done(null, user, { userId: userId });
where the first argument is the error object, the second is the current user, the third is the user id in DB, done () in turn returns the correct token. If you pass a single argument to done (), the argument is null, then the request status will be 401, and in the response we will get
lassName:"not-authenticated" code:401 errors:{} message:"Error" name:"NotAuthenticated"
And this would be the end of the deal, but you can also access our service via facebook. In order for this to be possible, the following should be added to the anonymous function:
app.configure(oauth2(Object.assign({ name: 'facebook', Strategy: FacebookStrategy, Verifier: CustomVerifierFB }, config.facebook)));
In this code, again, we are only interested in one parameter: “Verifier: CustomVerifierFB”. We, as in the case of local registration, extend the built-in class Verifier. When login via fb, the front does not send a request to a specific URL on the back-end, but follows the link, that is, on the front it will look like this:
<a href="/auth/facebook"> Fb</a>
If in a nutshell, after clicking on the link, a redirect will occur on the back, the back will be redirect to FB, the FB will be redirect to the back, the back will write the generated token to the cookie and send it to the front page. On the front, you need to parse the cookies and send the following requests to the back with a new token.
And there would not be this article, but I didn’t spend a lot of time on the question - How can I really know where the user came from?
The answer was quite simple. Before registering a component to log in via FB, do the following:
app.get('/auth/facebook', (req, res, next) => { referOrigin = req.headers.referer next(); }) app.configure(oauth2(Object.assign({ name: 'facebook', Strategy: FacebookStrategy, Verifier: CustomVerifierFB }, config.facebook)));
That is, we catch the transition to '/ auth / facebook', and write the value req.headers.referer to the global variable (referOrigin) and start the registration oauth2 (). Thus, we get the value of the host in the global variable and can use this value in the CustomVerifierFB class, which will look something like this:
class CustomVerifierFB extends Verifier { verify(req, accessToken, refreshToken, profile, done) { const refer = referOrigin let roles = '' if (refer === 'front_one') { roles = 'front_one' } else { roles = 'front_two' } return this.app.service('users').find({ query: { facebookId: profile.id, roles: roles } }).then(res => { if (res.data[0]) { done(null, res.data[0], { userId: res.data[0].id }); } else { return this.app.service('users').create({ facebookId: profile.id, email: profile._json.email, first_name: profile._json.first_name, last_name: profile._json.last_name, gender: profile._json.gender, avatar: profile._json.picture.data.url, roles: roles, isVerified: true, username: profile._json.email + 'whereFromUser' }).then(createRes => { done(null, createRes, { userId: createRes.id }); }) } }) } }
In the verify function, we did the following:
- this.app.service ('users'). find () - we are looking for whether there is a user with facebookId in the database that came to us as an answer from FB
- done (null, res.data [0], {userId: res.data [0] .id}) - if so, create a new token and return it to the front
- this.app.service ('users'). create () if not found, then create such a user and then call done ()
That's how I solved the problem of separating users for two different fronts.
PS - then I will write how I did password recovery for 2 user groups