How can I implement custom authorisation algorithm with MeteorJS?
When user types in his login and password in my application I send this credentials to external API (not google/facebook/etc it is private service) and that API responses me if this authorisation attempt was “ok” or “failed”. If it was “ok” then I use users login to sign any data that user creates or updates in my application but I do not want to store any data about this user on my side.
Please help me to find a way to do that.
I am really like meteors reactivity but I stuck on this point.
Not storing any user data is critical for my application.
@rjdavid thank you for your advise. I read this article 4 or 5 times already but still got issues.
I understood that I need to use packages accounts-base and accounts-password. Installed it via meteor add accounts-base accounts-password. On the client side I calling Meteor.loginWithPassword(login, password); in “submit” method of my auth form.
What I can’t understand is where do I need to call AccountsServer.validateLoginAttempt(myCheckFn) on the server side?
I tried to put it in “server” folder in file called “accounts.js” assuming that MeteorJS will automatically load it on startup. But it did not. Also I tried to import this “accounts.js” in “main.js” of “server” folder but still no luck.
When I fill the authorisation form in my app and hit submit button I see that Meteor.loginWithPassword(login, password); is firing and it gives me an error “403 user not found…” that makes me think that my “myCheckFn” isn’t firing because I don’t see any console.log that “myCheckFn” has.
I20181010-10:49:31.589(4)? Exception while invoking method 'login' { Error: Match error: Unknown key in field username
I20181010-10:49:31.589(4)? at check (packages/check/match.js:36:17)
I20181010-10:49:31.590(4)? at MethodInvocation.<anonymous> (packages/accounts-password/password_server.js:290:3)
I20181010-10:49:31.590(4)? at packages/accounts-base/accounts_server.js:483:32
I20181010-10:49:31.590(4)? at tryLoginMethod (packages/accounts-base/accounts_server.js:259:14)
I20181010-10:49:31.590(4)? at AccountsServer.Ap._runLoginHandlers (packages/accounts-base/accounts_server.js:480:18)
I20181010-10:49:31.590(4)? at MethodInvocation.methods.login (packages/accounts-base/accounts_server.js:543:27)
I20181010-10:49:31.590(4)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1767:12)
I20181010-10:49:31.590(4)? at DDP._CurrentMethodInvocation.withValue (packages/ddp-server/livedata_server.js:719:19)
I20181010-10:49:31.590(4)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20181010-10:49:31.590(4)? at DDPServer._CurrentWriteFence.withValue (packages/ddp-server/livedata_server.js:717:46)
I20181010-10:49:31.591(4)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20181010-10:49:31.591(4)? at Promise (packages/ddp-server/livedata_server.js:715:46)
I20181010-10:49:31.591(4)? at new Promise (<anonymous>)
I20181010-10:49:31.591(4)? at Session.method (packages/ddp-server/livedata_server.js:689:23)
I20181010-10:49:31.591(4)? at packages/ddp-server/livedata_server.js:559:43
I20181010-10:49:31.591(4)? message: 'Match error: Unknown key in field username',
I20181010-10:49:31.591(4)? path: 'username',
I20181010-10:49:31.591(4)? sanitizedError:
I20181010-10:49:31.591(4)? { Error: Match failed [400]
I20181010-10:49:31.592(4)? at errorClass.<anonymous> (packages/check/match.js:91:27)
I20181010-10:49:31.592(4)? at new errorClass (packages/meteor.js:725:17)
I20181010-10:49:31.592(4)? at check (packages/check/match.js:36:17)
I20181010-10:49:31.592(4)? at MethodInvocation.<anonymous> (packages/accounts-password/password_server.js:290:3)
I20181010-10:49:31.592(4)? at packages/accounts-base/accounts_server.js:483:32
I20181010-10:49:31.592(4)? at tryLoginMethod (packages/accounts-base/accounts_server.js:259:14)
I20181010-10:49:31.592(4)? at AccountsServer.Ap._runLoginHandlers (packages/accounts-base/accounts_server.js:480:18)
I20181010-10:49:31.593(4)? at MethodInvocation.methods.login (packages/accounts-base/accounts_server.js:543:27)
I20181010-10:49:31.593(4)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1767:12)
I20181010-10:49:31.593(4)? at DDP._CurrentMethodInvocation.withValue (packages/ddp-server/livedata_server.js:719:19)
I20181010-10:49:31.593(4)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20181010-10:49:31.593(4)? at DDPServer._CurrentWriteFence.withValue (packages/ddp-server/livedata_server.js:717:46)
I20181010-10:49:31.593(4)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20181010-10:49:31.593(4)? at Promise (packages/ddp-server/livedata_server.js:715:46)
I20181010-10:49:31.593(4)? at new Promise (<anonymous>)
I20181010-10:49:31.593(4)? at Session.method (packages/ddp-server/livedata_server.js:689:23)
I20181010-10:49:31.594(4)? isClientSafe: true,
I20181010-10:49:31.594(4)? error: 400,
I20181010-10:49:31.594(4)? reason: 'Match failed',
I20181010-10:49:31.594(4)? details: undefined,
I20181010-10:49:31.594(4)? message: 'Match failed [400]',
I20181010-10:49:31.594(4)? errorType: 'Meteor.Error' },
I20181010-10:49:31.594(4)? errorType: 'Match.Error' }
I20181010-10:49:31.595(4)? Sanitized and reported to the client as: { Error: Match failed [400]
I20181010-10:49:31.595(4)? at errorClass.<anonymous> (packages/check/match.js:91:27)
I20181010-10:49:31.595(4)? at new errorClass (packages/meteor.js:725:17)
I20181010-10:49:31.595(4)? at check (packages/check/match.js:36:17)
I20181010-10:49:31.595(4)? at MethodInvocation.<anonymous> (packages/accounts-password/password_server.js:290:3)
I20181010-10:49:31.595(4)? at packages/accounts-base/accounts_server.js:483:32
I20181010-10:49:31.595(4)? at tryLoginMethod (packages/accounts-base/accounts_server.js:259:14)
I20181010-10:49:31.595(4)? at AccountsServer.Ap._runLoginHandlers (packages/accounts-base/accounts_server.js:480:18)
I20181010-10:49:31.596(4)? at MethodInvocation.methods.login (packages/accounts-base/accounts_server.js:543:27)
I20181010-10:49:31.596(4)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1767:12)
I20181010-10:49:31.596(4)? at DDP._CurrentMethodInvocation.withValue (packages/ddp-server/livedata_server.js:719:19)
I20181010-10:49:31.596(4)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20181010-10:49:31.596(4)? at DDPServer._CurrentWriteFence.withValue (packages/ddp-server/livedata_server.js:717:46)
I20181010-10:49:31.596(4)? at Meteor.EnvironmentVariable.EVp.withValue (packages/meteor.js:1304:12)
I20181010-10:49:31.596(4)? at Promise (packages/ddp-server/livedata_server.js:715:46)
I20181010-10:49:31.596(4)? at new Promise (<anonymous>)
I20181010-10:49:31.596(4)? at Session.method (packages/ddp-server/livedata_server.js:689:23)
I20181010-10:49:31.596(4)? isClientSafe: true,
I20181010-10:49:31.596(4)? error: 400,
I20181010-10:49:31.597(4)? reason: 'Match failed',
I20181010-10:49:31.597(4)? details: undefined,
I20181010-10:49:31.597(4)? message: 'Match failed [400]',
I20181010-10:49:31.597(4)? errorType: 'Meteor.Error' }
And then I need to store info about token and users login in the session until user logs out.
I can’t find tutorials that covers my question and official documentation doesn’t helps. I don’t know which way of implementing this functionality is correct and which is not.
If I understand you correct, I have to do this check inside callback (second argument of Accounts.registerLoginHandler method), but how if it have to return a value that affects authorization process? I could return a promise there but I think it will not work. How can I make http request in synchronous manner?
You can make a synchronous-ish http request, just use Meteor’s HTTP helper.
Meteor uses fibers (thread-like co-routines) to make async calls behave in a synchronous fashion without blocking the event loop
// Thanks to fibers, this appears to run synchronously
// any error (including status code 4xx - 5xx) will immediately throw
const response = HTTP.post("http://my-local-auth-gate/sessions", {
data: { login: user, password: password },
});
I had a look through the accounts packages and we can simplify from the solutions posted so far:
import { HTTP } from "meteor/http";
Accounts.registerLoginHandler("custom", options => {
const { custom, password, user } = options;
// use an extra login method argument to check if this loginHandler should be used
if (!custom) return undefined; // return undefined to allow Meteor to try other login handlers
const response = HTTP.post("http://my-local-auth-gate/sessions", {
// data is automatically JSON.stringified and sent with correct content-type
data: { login: user, password: password },
});
const serviceName = "custom";
const serviceData = {
// id is a manditory key in serviceData
id: user,
// response.data has the parsed result of the request
// Not sure if you want to store the token but let's do it anyway
token: response.data.token,
};
// This creates or updates an internal user record that Meteor uses to track
// your login status. The function returns an object with the same schema
// for the expected return type of this funciotn, so we just return directly
return Accounts.updateOrCreateUserFromExternalService(
serviceName,
serviceData
// you can pass options which will be added to the user record
);
});
And on the client, you can create your own login handler
Meteor.loginWithCustom = (username, password, callback) => {
Accounts.callLoginMethod({
methodArguments: [
{
// add this to target your login handler
custom: true,
user: username,
password: password,
},
],
userCallback: (error, result) => {
if (error) {
if (callback) {
return callback(error);
}
throw error;
}
if (callback) callback();
},
});
};
const serviceData = {
// id is a manditory key in serviceData
id: user,
// response.data has the parsed result of the request
// Not sure if you want to store the token but let's do it anyway
token: response.data.token,
};