My goal is to consume an API which has already been deployed with nopCommerce (I do not have dev access to the server - I am just a user). There is a sample client application here, but the code is in C#. I have a webapp deployed on an Azure server using node.js. The API uses the OAuth 2.0 Authorization Code grant type.
I did a bit of Googling and it appears that client_credentials is typically used for this type of server to server flow:
How does 2-legged oauth work in OAuth 2.0?
Using OAuth for server-to-server authentication?
There is also an answer here which suggests that I can manually retrieve a token and then store it on the server. This is what I'm currently doing while testing my code.
I also found this answer which appears to be asking the same question. Like the author of that post, I am able to get a token via Postman, but my node.js code fails.
OAuth2.0 for nopCommerce
I wrote a minimal example in node.js here.
import { config } from 'dotenv';
import * as path from 'path';
import fetch from 'cross-fetch';
const ENV_FILE = path.join(__dirname, '.env');
const loadFromEnv = config({ path: ENV_FILE });
export async function getCodeUrl() {
const params = {
client_id: <CLIENT_ID>,
redirect_uri: 'http://example.com',
response_type: 'code',
};
console.log(params);
const url = new URL(`http://example.com/OAuth/Authorize`);
Object.keys(params).forEach(( key ) => url.searchParams.append(key, params[key]));
const res = await fetch(url.href, { method: 'GET' });
return res;
}
export async function getToken(code: string) {
const url = new URL(`http://example.com/api/token`);
const options = {
form: {
client_id: <CLIENT_ID>,
client_secret: <CLIENT_SECRET>,
code,
grant_type: 'authorization_code',
redirect_ui: 'http://example.com',
},
headers: { 'content-type': 'application/x-www-form-urlencoded' },
method: 'POST',
};
console.log(options);
const res = await fetch(url.href, options);
console.log('res', res);
return res;
}
const test = async () => {
const codeUrlString = (await getCodeUrl()).url;
const code = (new URL(codeUrlString).searchParams.get('code'));
if (code) {
console.log('code', code);
const tokenResponse = await getToken(code);
console.log('token res', tokenResponse);
}
};
test();
I am successfully able to retrieve the authorization code, but when I use that in a POST request to get a token, I get this error:
{ error: 'invalid_client' }
Related
I have a backend in Nodejs using Axios for my API calls. I need to implement Azure Authentication to get a token so I followed the sample below:
https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-nodejs-webapp-msal?WT.mc_id=Portal-Microsoft_AAD_RegisteredApps
The sample uses express and has redirects to first get and authorization and then a token, I have been trying to find a sample with Axios however I couldn't find one.
This is what I have so far, the idea is using the result to get a token,any guidance is much appreciate it.
const msal = require('#azure/msal-node');
const REDIRECT_URI = "http://localhost:3000/";
const LOGIN = "https://login.microsoftonline.com/";
const config = {
auth: {
clientId: "12345678910",
authority: "https://login.microsoftonline.com/12345678910",
clientSecret: "Secret",
knownAuthorities: ["https://login.microsoftonline.com/12345678910"
]
}
};
const pca = new msal.ConfidentialClientApplication(config);
module.exports = {
async getAzureAdToken(){
try {
let instance = axios.create({baseURL: LOGIN});
const authCodeUrlParameters = {
scopes: ["user.read"],
redirectUri: REDIRECT_URI
};
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) =>{
let url = response.substring(LOGIN.length);
instance.get(url).then((result) =>{
});
}).catch((error) => console.log(JSON.stringify(error)));
} catch (error) {
throw error
}
},
You could use client credentials flow to get access token with axios. Client credentials flow permits a web service (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling another web service. In the client credentials flow, permissions are granted directly to the application itself by an administrator. We need to add application permissions in API Permission.
Test in Postman:
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id=<client_id>
&scope=https://graph.microsoft.com/.default
&client_secret=<client_secret>
&grant_type=client_credentials
Code using Nodejs:
// Replace these values from the values of you app
const APP_ID = '[APP_ID/CLIENT_ID]';
const APP_SECERET = '[CLIENT_SECRET]';
const TOKEN_ENDPOINT ='https://login.microsoftonline.com/[TENANT_ID]/oauth2/v2.0/token';
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';
const axios = require('axios');
const qs = require('qs');
const postData = {
client_id: APP_ID,
scope: MS_GRAPH_SCOPE,
client_secret: APP_SECERET,
grant_type: 'client_credentials'
};
axios.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded';
let token = '';
axios
.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});
I am trying to setup a restricted firebase function that can be called from another client application that runs outside GCP. So far I failed to setup the client application authentication to get passed the restricted access on the firebase funciton.
Here is what I did and tried:
I created and deployed a simple helloWorld firebase function and verified that the function could be called from the client application with the default public access.
I removed allUsers from the helloWorld permissions on GCP and verified that the function could no longer be called from the client application (I get "403 Forbidden" in the response).
I created a new service account and added it as a member of "Cloud functions invoker" in the permissions panel of helloWorld on the GCP.
I created a new private json key file for this service account.
Then I followed the documentation to setup the client application authentication (see code below).
const fetch = require('node-fetch');
const jwt = require('jsonwebtoken');
async function main(){
// get unix timestamp in seconds
const current_time = Math.floor(Date.now() / 1000)
// get the service account key file
const service_account = require('./service_account.json');
// create the jwt body
const token_body = {
"iss": service_account.client_email,
"scope": "https://www.googleapis.com/auth/cloud-platform",
"aud": "https://oauth2.googleapis.com/token",
"exp": current_time + 3600,
"iat": current_time
}
// sign the token with the private key
const signed_token = jwt.sign(
token_body, service_account.private_key, { algorithm: 'RS256' }
)
// get an access token from the authentication server
const access_token = await fetch(
'https://oauth2.googleapis.com/token',
{
method: 'POST',
body: ''
+ 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer'
+ '&'
+ 'assertion=' + signed_token,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
).then(res => res.json()).then(body => body.access_token)
// call the firebase function with the Authorization header
return fetch(
url_hello_world, { headers: { 'Authorization': 'Bearer ' + access_token } }
).then(res => res.text()).then(console.log)
}
main().catch(console.error)
Unfortunately when I run the previous code I get "401 Unauthorize" with the following header:
www-authenticate: Bearer error="invalid_token" error_description="The access token could not be verified"
After that I tried another approach with the following tutorial (see code below).
const fetch = require('node-fetch');
const util = require('util');
const exec = util.promisify(require("child_process").exec)
async function main(){
// activate a service account with a key file
await exec('gcloud auth activate-service-account --key-file=' + key_file)
// retrieve an access token for the activated service account
const {stdout, stderr} = await exec("gcloud auth print-identity-token")
// get the access token from stdout and remove the new line character at the
// end of the string
const access_token = stdout.slice(0,-1)
// call the firebase function with the Authorization header
const response = await fetch(
url_hello_world,
{ headers: { 'Authorization': 'Bearer ' + access_token } }
)
// print the response
console.log(await response.text())
}
main().catch(console.error)
When I run this code, I get the expected response "Hello World" so the previous code can call the firebase function with the service account permission.
However, the client application that I target cannot rely on the gcloud cli and I am stuck to the point where I tried to understand what does not work in the first version above and what I need to change to make it works.
As you're using a Service Account JSON file, the following code can be helpful.
package.json
{
"name": "sample-call",
"version": "0.0.1",
"dependencies": {
"googleapis": "^62.0.0",
"node-fetch": "^2.6.1"
}
}
index.js
var { google } = require('googleapis')
const fetch = require('node-fetch')
const fs = require('fs');
let privatekey = JSON.parse(fs.readFileSync('key.json'));
let url_hello_world = 'CLOUD_FUNCTION_URL';
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
url_hello_world
)
async function main(){
jwtClient.authorize( async function(err, _token) {
if (err) {
console.log(err)
return err
} else {
const response = await fetch(
url_hello_world,
{
headers: { 'Authorization': 'Bearer ' + _token.id_token }
}
)
console.log(await response.text())
}
})
}
main().catch(console.error)
I would like to create an azure function using NodeJS and authenticate to Graph APIs. Through my reading I know that I have to use client credentials flow. I am using this code as posted by:
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void>
const APP_ID = [appId]';
const APP_SECERET = '[secret]';
const TOKEN_ENDPOINT ='https://login.microsoftonline.com/[tenantid]/oauth2/v2.0/token';
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';
const axios = require('axios');
const qs = require('qs');
const postData = {
client_id: APP_ID,
scope: MS_GRAPH_SCOPE,
client_secret: APP_SECERET,
grant_type: 'client_credentials'
};
axios.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded';
axios
.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
context.res = {
body: response.data //JSON.stringify(w, null, 4)
};
})
.catch(error => {
console.log(error);
});
};
As mentioned in this post: How to get users from azure active directory to azure function
However this is not working as it's not even making a request to Azure. Is there something missing? Can't I use MSAL.JS to make server to server calls when am using Node or is it just for web based apps and won't work with azure functions?
Most of the examples am seeing are related to .Net and they're using bunch of nuget packages, etc.. Isn't what I need supported in JavaScript azure functions?
Thanks.
I don't know why did you say it's not even making a request to azure, I test it with almost same code with yours and it work fine. I provide details of my steps as below for your reference.
1. I create a type script function in VS code (do not forget declare require in the second line of my code), my function code show as:
import { AzureFunction, Context, HttpRequest } from "#azure/functions"
declare var require: any
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const APP_ID = 'xxxxxx';
const APP_SECERET = 'xxxxxx';
const TOKEN_ENDPOINT ='https://login.microsoftonline.com/xxxxxx/oauth2/v2.0/token';
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';
const axios = require('axios');
const qs = require('qs');
const postData = {
client_id: APP_ID,
scope: MS_GRAPH_SCOPE,
client_secret: APP_SECERET,
grant_type: 'client_credentials'
};
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios
.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
console.log('=====below is response====');
console.log(response.data);
console.log('=====above is response====');
context.res = {
body: response.data
};
})
.catch(error => {
console.log(error);
});
};
export default httpTrigger;
2. I install axios and qs modules by the command:
npm install axios
npm install qs
3. To start the function, I ran the command:
npm install
npm start
4. After request the function, I get the result as:
How to implement OAuth 2 Circuit REST API for Bots? To use the client_id and client_secret. Thank you.
See https://circuit.github.io/oauth.html#client_credentials on the HTTP request to get the token. You can manually perform the /oauth/token request to get a token, or use any OAuth 2.0 library. The perform regular HTTP GET/POST requests using this OAuth token.
Here is an example that uses simple-oauth2 to get the token and then node-fetch to get the conversations.
const simpleOauth2 = require('simple-oauth2');
const fetch = require('node-fetch');
const DOMAIN = 'https://circuitsandbox.net';
const credentials = {
client: {
id: '<client_id>',
secret: '<cient_secret>'
},
auth: {
tokenHost: DOMAIN
}
};
// Initialize the OAuth2 Library
const oauth2 = simpleOauth2.create(credentials);
(async () => {
try {
const { access_token: token } = await oauth2.clientCredentials.getToken({scope: 'ALL'})
console.log('Access Token: ', token);
const convs = await fetch(`${DOMAIN}/rest/conversations`, {
headers: { 'Authorization': 'Bearer ' + token },
}).then(res => res.json());
console.log('Conversations:', convs);
} catch (err) {
console.error(err);
}
})();
Getting a user profile info through curl
curl -i https://www.googleapis.com/userinfo/v2/me -H "Authorization: Bearer a-google-account-access-token"
Getting a user profile info through node https get request
const https = require('https');
function getUserData(accessToken) {
var options = {
hostname: 'www.googleapis.com',
port: 443,
path: '/userinfo/v2/me',
method: 'GET',
json: true,
headers:{
Authorization: 'Bearer ' + accessToken
}
};
console.log(options);
var getReq = https.request(options, function(res) {
console.log("\nstatus code: ", res.statusCode);
res.on('data', function(response) {
try {
var resObj = JSON.parse(response);
console.log("response: ", resObj);
} catch (err) {
console.log(err);
}
});
});
getReq.end();
getReq.on('error', function(err) {
console.log(err);
});
}
var token = "a-google-account-access-token";
getUserData(token)
How can I use this google node api client library to get the user profile info provided that I already have the access token? I can use the code above to get the profile but I thought it's probably better off to use the google api library to do it, but I can't figure out how to do that using this node google api client library.
A temporary access token can be acquired through playing this playground
You can retrieve the user profile using the google node API client library. In this case, please retrieve the access token and refresh token as the scope of https://www.googleapis.com/auth/userinfo.profile. The sample script is as follows. When you use this sample, please set your ACCESS TOKEN.
Sample script :
var google = require('googleapis').google;
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2();
oauth2Client.setCredentials({access_token: 'ACCESS TOKEN HERE'});
var oauth2 = google.oauth2({
auth: oauth2Client,
version: 'v2'
});
oauth2.userinfo.get(
function(err, res) {
if (err) {
console.log(err);
} else {
console.log(res);
}
});
Result :
{
id: '#####',
name: '#####',
given_name: '#####',
family_name: '#####',
link: '#####',
picture: '#####',
gender: '#####',
locale: '#####'
}
If I misunderstand your question, I'm sorry.
2021 Solution
This answer may divert from the originally asked question but I think it will be useful for some people who are getting google user information in the backend by generating AuthUrl and sending it to the client side and then receiving the data response in the call back URL after the user gives permission from the client side.
Some global declarations
import { google } from "googleapis";
const Oauth2Client = new google.auth.OAuth2(
googleCredentials.CLIENT_ID,
googleCredentials.CLIENT_SECRET,
googleCredentials.REDIRECT_URI
);
Generate the Auth URL with the scopes
const SCOPE = [
'https://www.googleapis.com/auth/userinfo.profile', // get user info
'https://www.googleapis.com/auth/userinfo.email', // get user email ID and if its verified or not
];
const auth_url = Oauth2Client.generateAuthUrl({
access_type: "offline",
scope: SCOPE,
prompt: "consent",
state: "GOOGLE_LOGIN",
});
return res.json({ url: auth_url }); // send the Auth URL to the front end
Get the user data in the callback
let code = req.query.code; // get the code from req, need to get access_token for the user
let { tokens } = await Oauth2Client.getToken(code); // get tokens
let oauth2Client = new google.auth.OAuth2(); // create new auth client
oauth2Client.setCredentials({access_token: tokens.access_token}); // use the new auth client with the access_token
let oauth2 = google.oauth2({
auth: oauth2Client,
version: 'v2'
});
let { data } = await oauth2.userinfo.get(); // get user info
console.log(data);
Feel free to discuss in the comments if there's any confusion or error
You can also decode the id_token that's in the response from oAuth2Client.getToken().
Remember that you need to have necessary scopes enabled for your app for the response to have this id_token. For an example, let's say we just need the user's email. So we'll use,
https://www.googleapis.com/auth/userinfo.email
To decode the token,
const response = await oAuth2Client.getToken(code);
const userInfo = JSON.parse(Buffer.from(response.tokens.id_token.split('.')[1], 'base64').toString());
console.log(userInfo);
This will output,
{
"iss": "https://accounts.google.com",
"azp": "1234987819200.apps.googleusercontent.com",
"aud": "1234987819200.apps.googleusercontent.com",
"sub": "10769150350006150715113082367",
"at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
"hd": "example.com",
"email": "jsmith#example.com",
"email_verified": "true",
"iat": 1353601026,
"exp": 1353604926,
"nonce": "0394852-3190485-2490358"
}
This token simply is a JWT object which can be decoded eaisily. You can check this documentation and see even Google recommends this. The advantage of this method is that you don't have to make an extra request to Google APIs to get user's info.
Based on this anser: https://stackoverflow.com/a/46780714/140164
Here is another shorter way to achieve the same results.
const { google } = require('googleapis');
const oauth2Client = new google.auth.OAuth2()
const tokenInfo = await oauth2Client.getTokenInfo(*YourAccessToken*);
console.log(tokenInfo);