Meteor.js and Custom OpenId Connect server - meteor

How to do authentication via custom token server in Meteor.js?
Is there any package like accounts-google for custom token server which handles authentication by just taking token endpoints, client id, secrete, and scope as configuration parameter.

I don't know of a generic oauth package. But it shouldn't be too difficult to write a package for your particular server, as there are a number of examples to look at.
Using accounts-github as an example, here's the code for making the connection on the client. Note the endpoint URL, client id, scope, etc. This will handle the popup for you, but you'll probably want to include custom CSS:
var loginUrl =
'https://github.com/login/oauth/authorize' +
'?client_id=' + config.clientId +
'&scope=' + flatScope +
'&redirect_uri=' + OAuth._redirectUri('github', config) +
'&state=' + OAuth._stateParam(loginStyle, credentialToken);
OAuth.launchLogin({
loginService: "github",
loginStyle: loginStyle,
loginUrl: loginUrl,
credentialRequestCompleteCallback: credentialRequestCompleteCallback,
credentialToken: credentialToken,
popupOptions: {width: 900, height: 450}
});
And here's a snippet from the server side, completing the process to get an access token:
var getAccessToken = function (query) {
var config = ServiceConfiguration.configurations.findOne({service: 'github'});
if (!config)
throw new ServiceConfiguration.ConfigError();
var response;
try {
response = HTTP.post(
"https://github.com/login/oauth/access_token", {
headers: {
Accept: 'application/json',
"User-Agent": userAgent
},
params: {
code: query.code,
client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret),
redirect_uri: OAuth._redirectUri('github', config),
state: query.state
}
});
} catch (err) {
throw _.extend(new Error("Failed to complete OAuth handshake with Github. " + err.message),
{response: err.response});
}
if (response.data.error) { // if the http response was a json object with an error attribute
throw new Error("Failed to complete OAuth handshake with GitHub. " + response.data.error);
} else {
return response.data.access_token;
}
};
And utilizing the token to get the user identity:
var getIdentity = function (accessToken) {
try {
return HTTP.get(
"https://api.github.com/user", {
headers: {"User-Agent": userAgent}, // http://developer.github.com/v3/#user-agent-required
params: {access_token: accessToken}
}).data;
} catch (err) {
throw _.extend(new Error("Failed to fetch identity from Github. " + err.message),
{response: err.response});
}
};
The github and the accounts-github packages should be very helpful as references.

Related

How to implement Wordpress Application Password Authentication in Javascript async fetch?

I'm trying to setup a website using Wordpress as Headless CMS, using the built-in REST API. Using NuxtJS to fetch the data. Now I want to restrict API access so I enabled/created Wordpress Application Password Authentication.
However, I can not seem to find detailed information on how the URL should be assembled with authentication parameters to fetch data from API endpoint.
Credentials have to be added to the URL that's being fetched?
async asyncData ({ $config: { apiUrl, apiUser, apiPassword } }) {
try {
const products = await (await fetch(`${apiUrl}/producten`)).json()
return {
products
}
}
catch (error) {
console.log(error)
}
},
apiUrl, apiUser, apiPassword are currently in nuxtjs.config.js, under publicRuntimeConfig. But 1) they should come in privateRuntimeConfig?
And 2) getting following as return (which is the correct response from the WP Rest API, because I need to pass auth-credentials somewhere, somehow...)
{ "code": "rest_not_logged_in", "message": "You are not currently logged in.", "data": { "status": 401 } }
Solved by adding options to fetch;
const fetchHeaderOptions = {
cache: 'no-cache',
method: 'GET',
credentials: 'omit', //To instead ensure browsers don't include credentials in the request
mode: 'no-cors',
headers: {
'Authorization': 'Basic ' + encode(`${apiUser}` + ":" + `${apiPassword}`),
'Content-Type': 'application/json; charset=UTF-8; application/x-www-form-urlencoded',
},
}
const products = await (await fetch(`${apiUrl}/products`, fetchHeaderOptions)).json()

403 The caller does not have permission for Firebase Management API addFirebase

I want to add Firebase project through Firebase Management Api. So for that. I made project on Google Cloud Platform console. And created service account with permission as a owner.
I tried to read and create project throw google api explorer for addFirebase and it works. But when i try to do the same through my code it read availableProject successfully and give output as
{
"projectInfo": [
{
"project": "projects/firebase-api-238012",
"displayName": "Firebase-Api"
}
]
}
but when i try to add project it give me this error
{
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
}
I don't know why its is not creating project. What other permission it needs. And why it allowed to me read available projects first.
here is how i am trying to add my project.
jwt.js
const { google } = require('googleapis');
var serviceAccountJwt = require('./Firebase-Api-b0e41b85ad44.json');
exports.connect = async () => {
return new Promise((resolve, reject) => {
// scope is based on what is needed in our api
const scope = ['https://www.googleapis.com/auth/firebase', 'https://www.googleapis.com/auth/cloud-platform'];
// create our client with the service account JWT
const { client_email, private_key } = serviceAccountJwt;
const client = new google.auth.JWT(client_email, null, private_key, scope, null);
// perform authorization and resolve with the client
return client.authorize((err) => {
if (err) { reject(err) }
else {
resolve(client)
};
});
});
}
index.js file
const { google } = require('googleapis');
const request = require('request');
const { connect } = require('./jwt');
const availableProjects = 'https://firebase.googleapis.com/v1beta1/availableProjects';
async function getAccessToken() {
let client = await connect();
let accessToken = await client.getAccessToken();
let res = await getProjects(accessToken.token)
}
getAccessToken().catch(err => {
console.log(JSON.stringify(err))
})
const bodys = {
"timeZone": "America/Los_Angeles",
"locationId": "asia-south1",
"regionCode": "US"
}
async function getProjects(accesstoken) {
let options = {
url: availableProjects,
headers: {
'Authorization': 'Bearer ' + accesstoken,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}
return request(options, async function (err, res) {
if (err) {
console.error(err + " error");
} else {
//here it gives successful output
console.log("response")
console.log(res.body);
let bodyJson = JSON.parse(res.body);
let projectName = bodyJson.projectInfo[0].project;
console.log(projectName)
await addProject(accesstoken, projectName)
return res.body;
}
});
}
async function addProject(accesstoken, projecctID) {
fbUrl = getAddFBUrl(projecctID);
let options = {
url: fbUrl,
headers: {
'Authorization': 'Bearer ' + accesstoken,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body:JSON.stringify(bodys)
}
return request.post(options, function (err, res) {
if (err) {
console.error(err + " error");
} else {
//here in response out put as permission denied 403
console.log("response")
console.log(res.body);
console.log(JSON.stringify(res));
return res.body;
}
});
}
function getAddFBUrl(projectId) {
return 'https://firebase.googleapis.com/v1beta1/' + projectId +
':addFirebase';
}
i found one similar question to this. But it didn't helped me to resolve my issue which is here
AskFirebase
From the Firebase REST reference: Method: projects.addFirebase
To call projects.addFirebase, a member must be an Editor or Owner for
the existing GCP Project. Service accounts cannot call
projects.addFirebase.
Update:
To call projects.addFirebase, a project member or service account must have the following permissions (the IAM roles of Editor and Owner contain these permissions): firebase.projects.update, resourcemanager.projects.get, serviceusage.services.enable, and serviceusage.services.get.
https://firebase.google.com/docs/projects/api/reference/rest/v1beta1/projects/addFirebase
I'm not sure if my answer will be helpful for author of this question, but this if first two things all should check when facing 403 Error with Google Cloud APIs
0) Check configuration with gcloud
1) As mentioned before the first thing is to check the role of service account. You need Editor/Owner usually.
https://cloud.google.com/iam/docs/understanding-roles
https://console.cloud.google.com/iam-admin
2) The second one is to check if API enabled for project at all.
Also when creating a key check it for correct service account.
For someone who's just get started like me, this thing maybe helpful. When I seted up database, I choose Start in locked mode instead of Start in test mode. Therefore, I can't read or write :((. For beginner, just set everything in test mode. Hope it helpful.
https://i.stack.imgur.com/nVxjk.png
Your problem means that your project is not linked with your firebase account which means you have to login with your firebase account. Than you will have the permission
type cd functions in your firebase project directory
type firebase login
login with the Gmail which is connected with your firebase account
It'll work

How to recognise an existing Google OAuth authentication made via Firebase and perform a Google Directory API request in Angular 2?

I want to use my existing authentication and be able to use that same authentication to perform a get request to the Google Directory API. Here's my current code:
login() {
this.firebaseRef = new Firebase('https://xxx.firebaseio.com');
this.firebaseRef.authWithOAuthPopup("google", (error, authData) => {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
}, {
scope: "https://www.googleapis.com/auth/admin.directory.user.readonly"
});
}
getData() {
// TO-DO
// Recognise existing OAuth and perform a GET request to
// https://www.googleapis.com/admin/directory/v1/users?domain=nunoarruda.com
}
You could leverage the getAuth method on the firebaseRef instance. Something like that:
getData() {
var authData = this.firebaseRef.getData();
var provider = authData.provider;
// In your case provider contains "google"
}
See this documentation: https://www.firebase.com/docs/web/api/firebase/getauth.html.
I've found the solution. I need to use the current access token in the http request headers for the GET request.
import {Http, Headers} from 'angular2/http';
getData() {
// get access token
let authData = this.firebaseRef.getAuth();
let oauthToken = authData.google.accessToken;
console.log(oauthToken);
// set authorization on request headers
let headers = new Headers();
headers.append('Authorization', 'Bearer ' + oauthToken);
// api request
this.http.get('https://www.googleapis.com/admin/directory/v1/users?domain=nunoarruda.com',{headers: headers}).subscribe((res) => {
console.log(res.json());
});
}

Why is this core package being called?

I am trying to authenticate with Google through OAuth, and this is my server code
my.fetchTokens = function(code) {
var endpoint = 'https://accounts.google.com/o/oauth2/token';
var params = {
code: code,
client_id: Meteor.settings.google.CLIENT_ID,
client_secret: Meteor.settings.google.CLIENT_SECRET,
redirect_uri: Meteor.settings.google.REDIRECT_URL,
grant_type: 'authorization_code',
};
try {
response = HTTP.post(endpoint, { params: params });
} catch (err) {
throw _.extend(new Error("Failed to complete OAuth handshake with Google. " + err.message),
{response: err.response});
}
if (response.data.error) { // if the http response was a json object with an error attribute
throw new Error("Failed to complete OAuth handshake with Google. " + response.data);
}
var tokens = {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresIn: response.data.expires_in,
idToken: response.data.id_token
};
console.log(tokens);
return tokens;
};
But when I invoke this method, I get the following warnings
W20150316-10:30:05.853(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150316-10:30:05.854(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150316-10:30:05.855(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150316-10:30:05.855(1) (oauth_server.js:398) Error in OAuth Server: Match error: Expected string, got undefined
But I dont get why oauth_server.js:71 is being referenced
any ideas?
Probably because you're calling OAuth.openSecret or OAuth._redirectUri('google', config).
I assume one of these calls parses the current URL.

invalid_grant Google OAuth

I am trying to authentiate through Google's OAuth, but I'm having problems establishing a connection to their API
My client code:
'click #addChannel': function (event) {
event.preventDefault();
var userId = Meteor.userId();
var options = {
requestPermissions: [
'https://www.googleapis.com/auth/youtube',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/youtube.force-ssl',
'https://www.googleapis.com/auth/youtube.readonly',
'https://www.googleapis.com/auth/youtube.upload',
'https://www.googleapis.com/auth/youtubepartner',
'https://www.googleapis.com/auth/youtubepartner-channel-audit',
],
requestOfflineToken: true
};
Google.requestCredential(options, function(token) {
Meteor.call('userAddOauthCredentials', userId, token, function(error, result) {
if (error) {
throw error;
}
console.log(result);
});
});
My server code:
userAddOauthCredentials: function(userId, token) {
check(userId, String);
check(token, String);
var config = ServiceConfiguration.configurations.findOne({service: 'google'});
if (!config) {
throw new ServiceConfiguration.ConfigError();
}
console.log(token, config);
var endpoint = 'https://accounts.google.com/o/oauth2/token';
var params = {
code: token,
client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret),
redirect_uri: OAuth._redirectUri('google', config),
grant_type: 'authorization_code',
};
try { <------------------------------------------------------ this fails
response = HTTP.post(endpoint, { params: params });
} catch (err) {
throw _.extend(new Error("(first) Failed to complete OAuth handshake with Google. " + err.message),
{response: err.response});
}
if (response.data.error) { // if the http response was a json object with an error attribute
throw new Error("(second) Failed to complete OAuth handshake with Google. " + response.data);
} else {
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresIn: response.data.expires_in,
idToken: response.data.id_token
};
}
The above throws a [400] { "error" : "invalid_grant" } error.
Most of the above code I got from how the meteor accounts-google packages logs in a user (which works fine in my application). Link to that:
https://github.com/meteor/meteor/blob/87e3c6499d5eacce62f10faefe9ce49c77bb03ee/packages/google/google_server.js
Any advice on how to proceed from here?
Much appreciated
UPDATE1:
I get these warnings in my log
W20150318-09:11:42.532(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150318-09:11:42.532(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150318-09:11:42.533(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150318-09:11:42.534(1) (oauth_server.js:398) Error in OAuth Server: Match error: Expected string, got undefined
You have to parse your var params to application/x-www-form-urlencoded. Please find the below code to parse as i done in php
$fields_string="";
foreach($params as $key=>$value)
{
$fields_string .= $key.'='.$value.'&';
}
rtrim($fields_string, '&');
Now the $filed_string will contained the parse of params array.

Resources