Issues with OAuth 2.0 Library for Google Apps Scripts for MEETUP - oauth

I am using OAuth 2.0 Library for Google Spreadsheet for Meetup Api
Code.gs
function getMeetupService() {
return OAuth2.createService('meetup')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://secure.meetup.com/oauth2/authorize')
.setTokenUrl('https://secure.meetup.com/oauth2/access')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('.....')
.setClientSecret('......')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
.setTokenFormat(OAuth2.TOKEN_FORMAT.FORM_URL_ENCODED);
}
_eventId = null;
function startService(eventId) {
var meetupService = getMeetupService();
if (!meetupService.hasAccess()) {
var authorizationUrl = meetupService.getAuthorizationUrl();
return authorizationUrl;
} else {
//Yet to decide
}
return 123;
}
function authCallback(request) {
var meetupService = getMeetupService();
var isAuthorized = meetupService.handleCallback(request);
if (isAuthorized) {
//getAllDetails(_eventId);
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
Sidebar.html
$(function() {
checkService();
});
function checkService() {
google.script.run
.withSuccessHandler(function(url) {
if(url) {
$('body').append('Click here to Login into Meetup');
$('#run-get-details').attr("onclick", 'window.open("' + url + '")');
} else {
$('#run-get-details').attr("url", '');
}
$('#run-get-details').prop("disabled", false);
})
.withFailureHandler(failureHandler)
.startService($("#eventId").val());
}
function authCallback(request) {
alert(123);
}
Everything seems to be working till the link. Once the link is clicked it opens the authentication window of the meetup exactly i want. But then once I authorize the app in meetup and when it gets redirected to script.google.com
https://script.google.com/a/macros/{domain}/d/{PROJECT_ID}/usercallback?code={code}&state=ADEpC8wvspQd-+VXyQ0mOhYNenFu9Ib08hK6xfJ2gNJwcTxrIB4nVfphDyhejUHEMdgD0OhtwdcEs46KdhUZMD-Ekwftj3bzXwdi-mKc8PLKd7SsAYYqE-ZVZD2tm1HmyxtKYJCkoeg7R1K5DIMedYp38BiJ4F2Hbtei34fEdveObQhMSMDt1f2ufrmDzGh5W+fxdXMEmrfeCINO23hC8yV0JRXVDkErL3t8pQih8DicQoY6k2uqHThK8BqfSioBkgPZ0SPvj3Krtpgj9R+XWGtPqRRAwPum4k8etMROvy2DLT1ENNJdrVw
But then its throwing the error
The state token is invalid or has expired. Please try again.
Someone please help me in this.

So looking into this a bit further I found the issue. The Meetup server is changing the state token. If you copy your token from getAuthorizationUrl() and paste it into the state parameter in the url of the bad callback the Auth flow will successfully continue.
State Token made by Apps Script
ADEpC8zI8-E38GrIi2sB5Pf1xK2Hn-6XQ-SJLa0gmxos6z-hbvedTJ2UvXzSJxXbE_NfJlpHkOsjN4DLJ0sOGsYIZpTBc3OpAMWuoj-8UjUuKcTs1htZknyzv0QX9pPe-McxB_MC1fbGBjvrwEGP5_58tQdfRf3K70LURbe0cZ2qx_YK5xxN2qE
Returned State Token
ADEpC8zI8-E38GrIi2sB5Pf1xK2Hn-6XQ-SJLa0gmxos6z-hbvedTJ2UvXzSJxXbE+NfJlpHkOsjN4DLJ0sOGsYIZpTBc3OpAMWuoj-8UjUuKcTs1htZknyzv0QX9pPe-McxB+MC1fbGBjvrwEGP5+58tQdfRf3K70LURbe0cZ2qx+YK5xxN2qE

Related

Use MSAL in Microsoft Teams not work JS

I work on an MS Teams app with a connection to MS Graph.
I'm trying to get an access token for MS Graph in MS Teams. To get a token I'm using MSAL js.
If I run the App with gulp serve I receive a valid token and I have access to the MS Graph endpoints. But if I build the app and install it in MS Teams the function userAgentApplication.acquireTokenSilent(config) is never executed. I tested it with a console.log before and after the call. There is no error thrown.
Do you have any idea why the above snippet is not executed in MS Teams (app and webapp)?
NEW:
On Home:
export function login() {
const url = window.location.origin + '/login.html';
microsoftTeams.authentication.authenticate({
url: url,
width: 600,
height: 535,
successCallback: function(result: string) {
console.log('Login succeeded: ' + result);
let data = localStorage.getItem(result) || '';
localStorage.removeItem(result);
let tokenResult = JSON.parse(data);
storeToken(tokenResult.accessToken)
},
failureCallback: function(reason) {
console.log('Login failed: ' + reason);
}
});
}
On Login
microsoftTeams.initialize();
// Get the tab context, and use the information to navigate to Azure AD login page
microsoftTeams.getContext(function (context) {
// Generate random state string and store it, so we can verify it in the callback
let state = _guid();
localStorage.setItem("simple.state", state);
localStorage.removeItem("simple.error");
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols-implicit
// for documentation on these query parameters
let queryParams = {
client_id: "XXX",
response_type: "id_token token",
response_mode: "fragment",
scope: "https://graph.microsoft.com/User.Read openid",
redirect_uri: window.location.origin + "/login-end.html",
nonce: _guid(),
state: state,
login_hint: context.loginHint,
};
// Go to the AzureAD authorization endpoint (tenant-specific endpoint, not "common")
// For guest users, we want an access token for the tenant we are currently in, not the home tenant of the guest.
let authorizeEndpoint = `https://login.microsoftonline.com/${context.tid}/oauth2/v2.0/authorize?` + toQueryString(queryParams);
window.location.assign(authorizeEndpoint);
});
// Build query string from map of query parameter
function toQueryString(queryParams) {
let encodedQueryParams = [];
for (let key in queryParams) {
encodedQueryParams.push(key + "=" + encodeURIComponent(queryParams[key]));
}
return encodedQueryParams.join("&");
}
// Converts decimal to hex equivalent
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _decimalToHex(number) {
var hex = number.toString(16);
while (hex.length < 2) {
hex = '0' + hex;
}
return hex;
}
// Generates RFC4122 version 4 guid (128 bits)
// (From ADAL.js: https://github.com/AzureAD/azure-activedirectory-library-for-js/blob/dev/lib/adal.js)
function _guid() {...}
on login end
microsoftTeams.initialize();
localStorage.removeItem("simple.error");
let hashParams = getHashParameters();
if (hashParams["error"]) {
// Authentication/authorization failed
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure(hashParams["error"]);
} else if (hashParams["access_token"]) {
// Get the stored state parameter and compare with incoming state
let expectedState = localStorage.getItem("simple.state");
if (expectedState !== hashParams["state"]) {
// State does not match, report error
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("StateDoesNotMatch");
} else {
// Success -- return token information to the parent page.
// Use localStorage to avoid passing the token via notifySuccess; instead we send the item key.
let key = "simple.result";
localStorage.setItem(key, JSON.stringify({
idToken: hashParams["id_token"],
accessToken: hashParams["access_token"],
tokenType: hashParams["token_type"],
expiresIn: hashParams["expires_in"]
}));
microsoftTeams.authentication.notifySuccess(key);
}
} else {
// Unexpected condition: hash does not contain error or access_token parameter
localStorage.setItem("simple.error", JSON.stringify(hashParams));
microsoftTeams.authentication.notifyFailure("UnexpectedFailure");
}
// Parse hash parameters into key-value pairs
function getHashParameters() {
let hashParams = {};
location.hash.substr(1).split("&").forEach(function (item) {
let s = item.split("="),
k = s[0],
v = s[1] && decodeURIComponent(s[1]);
hashParams[k] = v;
});
return hashParams;
}
Even though my answer is quite late, but I faced the same problem recently.
The solution is farely simple: MSALs silent login does not work in MSTeams (yet) as MSTeams relies on an IFrame Approach that is not supported by MSAL.
You can read all about it in this Github Issue
Fortunately, they are about to release a fix for this in Version msal 1.2.0 and there is already an npm-installable beta which should make this work:
npm install msal#1.2.0-beta.2
Update: I tried this myself - and the beta does not work as well. This was confirmed by Microsoft in my own Github Issue.
So I guess at the time being, you simply can't use MSAL for MS Teams.

accessing Twitter API from Google Apps Script

I'm trying to read in a Google sheet my Twitter timeline.
I've copied the following code reported in the GAS documentation about twitter authentication (omitting step 2 since I'm not using the code inside a UI):
function getTwitterService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth1.createService('twitter')
// Set the endpoint URLs.
.setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
.setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
.setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
// Set the consumer key and secret.
.setConsumerKey('mykey')
.setConsumerSecret('mysecret')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties());
}
function authCallback(request) {
var twitterService = getTwitterService();
var isAuthorized = twitterService.handleCallback(request);
if (isAuthorized) {
return Logger.log('Success! You can close this tab.');
} else {
return Logger.log('Denied. You can close this tab');
}
}
function makeRequest() {
var twitterService = getTwitterService();
var response = twitterService.fetch('https://api.twitter.com/1.1/statuses/user_timeline.json');
Logger.log(response);
}
but I obtain the message error: Service not authorized. (row 292, file "Service", project "OAuth1").
What's wrong?
I needed to add the following line the first time I execute makeRequest:
var authorizationUrl = twitterService.authorize();
Logger.log(authorizationUrl);
Then, open the url read from the log and authorize the app.
After that, all works fine.

How do I register a JWT token expired event with AngularFire? (AngularFire Custom Authentication)

How do I register a JWT token expired event with AngularFire?
Right now I have my Rails server sending a JWT to the AngularJS client, which is set to expire in 60 seconds. I'm connecting to Firebase on my client successfully. After 60 seconds, it expires and I get a "FIREBASE WARNING: auth() was canceled: Auth token is expired." message in my browser console.
It is not clear to me what to watch for this event. My goal would be to call my server for a new token when my current one expires.
client code snippet: here is me initializing a firebase obj and watching a url
var ref = new Firebase(URL);
// Create a callback which logs the current auth state
var authDataCallback = function (authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out", authData);
}
}
// Register the callback to be fired every time auth state changes
ref.onAuth(authDataCallback);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
var firebaseBal = $firebase(ref).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);
server code snippet (I use firebase-token-generator to generate my JWT token):
generator = Firebase::FirebaseTokenGenerator.new(secret);
payload = { :uid => "123" }
# expired in a minute, for testing
expires = Time.now.to_i + (60*1)
options = {:expires => expires}
token = generator.create_token(payload, options)
// this is then sent to the client
Problem to be solved soon, see GitHub issue - https://github.com/firebase/angularfire/issues/514
This GitHub issue exists for the exact reason to help you accomplish
what you want to accomplish :) The short answer is that we don't
really support this that well right now.
For the time being, you will have to use $extendFactory() and put your
logic in the $$error()method. Here are the slim docs on that. The
$$error() method will fire once authentication fails due to token
expiry or security rules.
You should see improvements in this area in the coming weeks and this
is tagged for the 1.0.0 release.
var ref = new Firebase(URL);
var authHandler = function(error, authData) {
if(error) {
console.log("Login Failed!", error);
} else {
console.log("Login Succeeded!", authData);
// create a factory to pass into $firebase
var timeoutErrorFactory = $FirebaseObject.$extendFactory({
$$error: function(data) {
// TODO: not sure if super needs to be applied
// var err = $FirebaseObject.prototype.$$error.apply(this, arguments);
console.log('firebase timeout error cleanup', data.code);
// TODO: call server for a new token
}
});
// create an instance which uses the customized factory
firebaseBal = $firebase(ref, {objectFactory: timeoutErrorFactory}).$asObject();
// update balance when val changes
firebaseWatch = firebaseBal.$watch(function(newVal, oldVal) {
$scope.balance = firebaseBal.$value;
});
}
}
// Authenticate users with a custom Firebase token
ref.authWithCustomToken(Auth.firebaseToken(), authHandler);

Setting the redirect_uri in Asp.Net Identity

I am trying to set the redirect_uri for the facebook login with Asp.Net Identity. However, the GetExternalLogin REST method in the AccountController is only triggered if the redirect_uri is '/'. If I add anything else it does not trigger GetExternalLogin, the browser only shows error: invalid_request.
However the url contains the redirected parameter as it should e.g. if I add the redirect_uri as http://localhost:25432/testing
the response URL looks like this:
http://localhost:25432/api/Account/ExternalLogin?provider=Facebook&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A25432%2Ftesting&state=0NctHHGq_aiazEurHYbvJT8hDgl0GJ_GGSdFfq2z5SA1
and the browser window shows: error: invalid_request
Any idea why this works only when redirecting to '/' but not to any other urlĀ“s?
For anyone else that might run into this issue: the problem is when you take (copy) the ApplicationOAuthProvider.cs from the Visual Studio SPA template and it is there where this code is:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
var expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
This will obviously block any redirect_uri that doesn't look like http://localhost/ or http://example.com/ so for instance http://example.com/home won't work.
Now this below is the source for InvokeAuthorizeEndpointAsync in Katana which does all the work and you can see it calls into any custom OAuthProvider that might be registered for this MVC/Web API application (this registration typically happens in Startup.Auth.cs):
private async Task<bool> InvokeAuthorizeEndpointAsync()
{
var authorizeRequest = new AuthorizeEndpointRequest(Request.Query);
var clientContext = new OAuthValidateClientRedirectUriContext(
Context,
Options,
authorizeRequest.ClientId,
authorizeRequest.RedirectUri);
if (!String.IsNullOrEmpty(authorizeRequest.RedirectUri))
{
bool acceptableUri = true;
Uri validatingUri;
if (!Uri.TryCreate(authorizeRequest.RedirectUri, UriKind.Absolute, out validatingUri))
{
// The redirection endpoint URI MUST be an absolute URI
// http://tools.ietf.org/html/rfc6749#section-3.1.2
acceptableUri = false;
}
else if (!String.IsNullOrEmpty(validatingUri.Fragment))
{
// The endpoint URI MUST NOT include a fragment component.
// http://tools.ietf.org/html/rfc6749#section-3.1.2
acceptableUri = false;
}
else if (!Options.AllowInsecureHttp &&
String.Equals(validatingUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
{
// The redirection endpoint SHOULD require the use of TLS
// http://tools.ietf.org/html/rfc6749#section-3.1.2.1
acceptableUri = false;
}
if (!acceptableUri)
{
clientContext.SetError(Constants.Errors.InvalidRequest);
return await SendErrorRedirectAsync(clientContext, clientContext);
}
}
await Options.Provider.ValidateClientRedirectUri(clientContext);
if (!clientContext.IsValidated)
{
_logger.WriteVerbose("Unable to validate client information");
return await SendErrorRedirectAsync(clientContext, clientContext);
}
var validatingContext = new OAuthValidateAuthorizeRequestContext(
Context,
Options,
authorizeRequest,
clientContext);
if (string.IsNullOrEmpty(authorizeRequest.ResponseType))
{
_logger.WriteVerbose("Authorize endpoint request missing required response_type parameter");
validatingContext.SetError(Constants.Errors.InvalidRequest);
}
else if (!authorizeRequest.IsAuthorizationCodeGrantType &&
!authorizeRequest.IsImplicitGrantType)
{
_logger.WriteVerbose("Authorize endpoint request contains unsupported response_type parameter");
validatingContext.SetError(Constants.Errors.UnsupportedResponseType);
}
else
{
await Options.Provider.ValidateAuthorizeRequest(validatingContext);
}
if (!validatingContext.IsValidated)
{
// an invalid request is not processed further
return await SendErrorRedirectAsync(clientContext, validatingContext);
}
_clientContext = clientContext;
_authorizeEndpointRequest = authorizeRequest;
var authorizeEndpointContext = new OAuthAuthorizeEndpointContext(Context, Options);
await Options.Provider.AuthorizeEndpoint(authorizeEndpointContext);
return authorizeEndpointContext.IsRequestCompleted;
}
This is key:
await Options.Provider.ValidateClientRedirectUri(clientContext);
So your solution is to change how the ValidateClientRedirectUri performs the validation - the default SPA implementation is, as you can see, very naive.
There's lots of ppl having issues with SPA mainly because it lacks any kind of useful information and I mean that both for ASP.NET Identity and OWIN stuff and with regards to what is going on within KnockoutJS implementation.
I wish Microsoft would provide more comprehensive docs for these templates because anyone who will try to do anything a bit more complex will run into issues.
I've spent hours on this, digging into OWIN (Katana) source code thinking it is the above implementation that blocks my redirect URIs but it was not, hopefully helps someone else too.
HTH
The problem is that GetExternalLogin registered as OAuthOptions.AuthorizeEndpointPath which used for app.UseOAuthBearerTokens(OAuthOptions). This configuration puts validation on arguments of endpoint.
if (!Uri.TryCreate(authorizeRequest.RedirectUri, UriKind.Absolute, out validatingUri))
{
// The redirection endpoint URI MUST be an absolute URI
}
else if (!String.IsNullOrEmpty(validatingUri.Fragment))
{
// The endpoint URI MUST NOT include a fragment component.
}
else if (!Options.AllowInsecureHttp &&
String.Equals(validatingUri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
{
// The redirection endpoint SHOULD require the use of TLS
}
And you should pass "Authorize endpoint request missing required response_type parameter" and
"Authorize endpoint request contains unsupported response_type parameter"
Based on the other answers, I changed the Validation code in ApplicationOAuthProvider.cs to just ensure that the redirect uri is on the same domain like so:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (context.RedirectUri.StartsWith(expectedRootUri.AbsoluteUri))
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}

How can I login to Meteor with native device Facebook?

Suppose I logged into my device's Facebook authentication, like system Facebook on iOS. I obtain an access token.
How can I use the access token to login to Meteor's Facebook Oauth provider?
To login with Facebook using an access token obtained by another means, like iOS Facebook SDK, define a method on the server that calls the appropriate Accounts method:
$FB = function () {
if (Meteor.isClient) {
throw new Meteor.Error(500, "Cannot run on client.");
}
var args = Array.prototype.slice.call(arguments);
if (args.length === 0) {
return;
}
var path = args[0];
var i = 1;
// Concatenate strings together in args
while (_.isString(args[i])) {
path = path + "/" + args[i];
i++;
}
if (_.isUndefined(path)) {
throw new Meteor.Error(500, 'No Facebook API path provided.');
}
var FB = Meteor.npmRequire('fb');
var fbResponse = Meteor.sync(function (done) {
FB.napi.apply(FB, [path].concat(args.splice(i)).concat([done]));
});
if (fbResponse.error !== null) {
console.error(fbResponse.error.stack);
throw new Meteor.Error(500, "Facebook API error.", {error: fbResponse.error, request: args});
}
return fbResponse.result;
};
Meteor.methods({
/**
* Login to Meteor with a Facebook access token
* #param accessToken Your Facebook access token
* #returns {*}
*/
facebookLoginWithAccessToken: function (accessToken) {
check(accessToken, String);
var serviceData = {
accessToken: accessToken
};
// Confirm that your accessToken is you
try {
var tokenInfo = $FB('debug_token', {
input_token: accessToken,
access_token: Meteor.settings.facebook.appId + '|' + Meteor.settings.facebook.secret
});
} catch (e) {
throw new Meteor.Error(500, 'Facebook login failed. An API error occurred.');
}
if (!tokenInfo.data.is_valid) {
throw new Meteor.Error(503, 'This access token is not valid.');
}
if (tokenInfo.data.app_id !== Meteor.settings.facebook.appId) {
throw new Meteor.Error(503, 'This token is not for this app.');
}
// Force the user id to be the access token's user id
serviceData.id = tokenInfo.data.user_id;
// Returns a token you can use to login
var loginResult = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});
// Login the user
this.setUserId(loginResult.userId);
// Return the token and the user id
return loginResult;
}
}
This code depends on the meteorhacks:npm package. You should call meteor add meteorhacks:npm and have a package.json file with the Facebook node API: { "fb": "0.7.0" }.
If you use demeteorizer to deploy your app, you will have to edit the output package.json and set the scrumptious dependency from "0.0.1" to "0.0.0".
On the client, call the method with the appropriate parameters, and you're logged in!
In Meteor 0.8+, the result of Accounts.updateOrCreateUserFromExternalService has changed to an object containing {userId: ...} and furthermore, no longer has the stamped token.
You can get the accessToken in the Meteor.user() data at Meteor.user().services.facebook.accessToken (be aware this can only be accessed on the server side as the services field is not exposed to the client.
So when a user logs in with facebook on your meteor site these fields would be populated with the user's facebook data. If you check your meteor user's database with mongo or some other gui tool you could see all the fields which you have access to.
Building on DrPangloss' most excellent answer above, combining it with this awesome post: http://meteorhacks.com/extending-meteor-accounts.html
You'll run into some issues using ObjectiveDDP in trying to get the client persist the login. Include the header:
#import "MeteorClient+Private.h"
And manually set the required internals. Soon I'll make a meteorite package and an extension to MyMeteor (https://github.com/premosystems/MyMeteor) but for now it's manual.
loginRequest: {"accessToken":"XXXXXb3Qh6sBADEKeEkzWL2ItDon4bMl5B8WLHZCb3qfL11NR4HKo4TXZAgfXcySav5Y8mavDqZAhZCZCnDDzVbdNmaBAlVZAGENayvuyStkTYHQ554fLadKNz32Dym4wbILisPNLZBjDyZAlfSSgksZCsQFxGPlovaiOjrAFXwBYGFFZAMypT9D4qcZC6kdGH2Xb9V1yHm4h6ugXXXXXX","fbData":{"link":"https://www.facebook.com/app_scoped_user_id/10152179306019999/","id":"10152179306019999","first_name":"users' first name","name":"user's Full Name","gender":"male","last_name":"user's last name","email":"users#email.com","locale":"en_US","timezone":-5,"updated_time":"2014-01-11T23:41:29+0000","verified":true}}
Meteor.startup(
function(){
Accounts.registerLoginHandler(function(loginRequest) {
//there are multiple login handlers in meteor.
//a login request go through all these handlers to find it's login hander
//so in our login handler, we only consider login requests which has admin field
console.log('loginRequest: ' + JSON.stringify(loginRequest));
if(loginRequest.fbData == undefined) {
return undefined;
}
//our authentication logic :)
if(loginRequest.accessToken == undefined) {
return null;
} else {
// TODO: Verfiy that the token from facebook is valid...
// https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.0#checktoken
// graph.facebook.com/debug_token? input_token={token-to-inspect}&access_token={app-token-or-admin-token}
}
//we create a user if not exists, and get the userId
var email = loginRequest.fbData.email || "-" + id + "#facebook.com";
var serviceData = {
id: loginRequest.fbData.id,
accessToken: loginRequest.accessToken,
email: email
};
var options = {
profile: {
name: loginRequest.fbData.name
}
};
var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options);
console.log('Logged in from facebook: ' + user.userId);
//send loggedin user's user id
return {
userId: user.userId
}
});
}
);
This answer could be improved further as we can now directly debug the token from a REST http request using futures. Credit still goes to #DoctorPangloss for the principal steps necessary.
//Roughly like this - I removed it from a try/catch
var future = new Future();
var serviceData = {
accessToken: accessToken,
email: email
};
var input = Meteor.settings.private.facebook.id + '|' + Meteor.settings.private.facebook.secret
var url = "https://graph.facebook.com/debug_token?input_token=" + accessToken + "&access_token=" + input
HTTP.call( 'GET', url, function( error, response ) {
if (error) {
future.throw(new Meteor.Error(503, 'A error validating your login has occured.'));
}
var info = response.data.data
if (!info.is_valid) {
future.throw(new Meteor.Error(503, 'This access token is not valid.'));
}
if (info.app_id !== Meteor.settings.private.facebook.id) {
future.throw(new Meteor.Error(503, 'This token is not for this app.'));
}
// Force the user id to be the access token's user id
serviceData.id = info.user_id;
// Returns a token you can use to login
var user = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, {});
if(!user.userId){
future.throw(new Meteor.Error(500, "Failed to create user"));
}
//Add email & user details if necessary
Meteor.users.update(user.userId, { $set : { fname : fname, lname : lname }})
Accounts.addEmail(user.userId, email)
//Generate your own access token!
var token = Accounts._generateStampedLoginToken()
Accounts._insertLoginToken(user.userId, token);
// Return the token and the user id
future.return({
'x-user-id' : user.userId,
'x-auth-token' : token.token
})
});
return future.wait();
Use this instead of the JS lib suggested by #DoctorPangloss. Follow the same principles he suggested but this avoids the need to integrate an additional library

Resources