Using loopback, I have created a connection to an existing API using the REST connector, which is working well. I would however like to pass through the oAuth token coming from the client.
I can get hold of the oAuth token by grabbing ctx.req.headers.authorization from the Model.beforeRemote method, but can't seem to figure out a way of passing it to the REST connector as a new header.
I've tried a couple of things:
Adding a hook using Model.observe (but this doesn't seem to fire with the REST connector).
Using a template with an authorization field - but have not been able to get this working correctly.
Any ideas appreciated.
With the connector below you should be able to pass the OAuth token into the function (as first parameter in the example). Does something like this not work for you?
{
connector: 'rest',
debug: false,
options: {
"headers": {
"accept": "application/json",
"content-type": "application/json",
"authorization": "{oauth}"
},
strictSSL: false,
},
operations: [
{
template: {
"method": "GET",
"url": "http://maps.googleapis.com/maps/api/geocode/{format=json}",
"query": {
"address": "{street},{city},{zipcode}",
"sensor": "{sensor=false}"
},
"options": {
"strictSSL": true,
"useQuerystring": true
},
"responsePath": "$.results[0].geometry.location"
},
functions: {
"geocode": ["oauth", "street", "city", "zipcode"]
}
}
]}
Wanted to answer this, and build on Bryan's comments. Firstly, in datasources.json, you'll want to setup the REST connector:
{
"name": "connect",
"connector": "rest",
"debug": "true",
"operations": [
{
"template": {
"method": "GET",
"url": "http://server/api",
"headers":{
"authorization": "Bearer {token}"
}
},
"functions": {
"get": ["token"]
}
}
]
}
As Bryan covered, it possible to put the auth header in each call, or at the root of the connector.
Secondly, and this is the bit I was stuck on, in order to pass the token to the API call from a model, it's required to generate a remote method that passes the token as a query parameter. This is what it looks like in this example:
module.exports = function (Model) {
Model.disableRemoteMethod('invoke', true);
Model.disableRemoteMethod('get', true);
Model.call = function (req, cb) {
var token = req.token;
Model.get(token, function (err, result) {
cb(null, result);
});
};
Model.remoteMethod(
'call',
{
http: {path: '/', verb: 'get'},
accepts: [
{arg: 'req', type: 'object', http: {source: 'req'}}
],
returns: {
root: true
}
}
);
};
Notice how the req argument is required in order to provide the request to the model. You also notice that I've disabled the original get and invoke methods (replacing it with a more REST-friendly resource).
Finally, you'll need to get the token into the request. For this, it's easy enough to use some middleware. Here's an example from server.js:
app.use('/api', function (req, res, next) {
oidc.authenticate(req, function (err, token) {
if (err) {
return res.send({status: 401, message: err});
}
req.token = token;
next();
});
});
In the above example, I'm using an internal OIDC provider to validate the token, but of course, you can use anything.
Related
I am trying to add custom oauth provider to my next.js app. I am adding custom provider in [...nextauth].js:
export default NextAuth({
// Configure one or more authentication providers
providers: [
{
id: "moneybutton",
name: "Money Button",
type: "oauth",
version: "2.0",
scope: "auth.user_identity:read users.profiles:read users.profiles.email:read users.balance:read",
params: {
grant_type: "authorization_code"
},
accessTokenUrl: "https://www.moneybutton.com/oauth/v1/token",
requestTokenUrl: "https://www.moneybutton.com/oauth/v1/token",
authorizationUrl: "https://www.moneybutton.com/oauth/v1/authorize?response_type=code",
profileUrl: "https://www.moneybutton.com/api/v1/auth/user_identity",
profile(profile) {
return {
id: profile.data.attributes.id,
name: profile.data.attributes.name,
};
},
clientId: 'my_oauth_identifier',
clientSecret: 'my_client_secret'
}
// ...add more providers here
],
debug: true
});
OAuth flow seems to work correct, as i am seeing my profile id coming back in responses but it finishes on http://localhost:3000/api/auth/signin?error=Callback
I set debug to true and i am getting following errors:
[next-auth][error][oauth_get_access_token_error]
https://next-auth.js.org/errors#oauth_get_access_token_error {
statusCode: 400,
data: '{"errors":[{"id":"6da534f0-a512-11eb-92e8-891975d02f44","status":400,"title":"Bad Request","detail":"Invalid client: client is invalid"}],"jsonapi":{"version":"1.0"}}'
} undefined undefined
[next-auth][error][oauth_get_access_token_error]
https://next-auth.js.org/errors#oauth_get_access_token_error {
statusCode: 400,
data: '{"errors":[{"id":"6da534f0-a512-11eb-92e8-891975d02f44","status":400,"title":"Bad Request","detail":"Invalid client: client is invalid"}],"jsonapi":{"version":"1.0"}}'
} moneybutton 9f3970b8ae39f9d46f9fae56f6fb6135ecb7e87b
[next-auth][error][oauth_callback_error]
https://next-auth.js.org/errors#oauth_callback_error {
statusCode: 400,
data: '{"errors":[{"id":"6da534f0-a512-11eb-92e8-891975d02f44","status":400,"title":"Bad Request","detail":"Invalid client: client is invalid"}],"jsonapi":{"version":"1.0"}}'
It says that client is invalid, but i am sure oauth identifier and secret are correct, as well as redirect URL set to http://localhost:3000/api/auth/callback/moneybutton.
response for profile looks like this if it would be helpful:
{
"data": {
"id": "75101",
"type": "user_identities",
"attributes": {
"id": "75101",
"name": "John Doe"
}
},
"jsonapi": {
"version": "1.0"
}
}
Links to documentations:
https://next-auth.js.org/configuration/providers
https://docs.moneybutton.com/docs/api/v1/api-v1-user-identity
I don't know if it's some bug or my approach is wrong and will apreciate any help
I have been trying to connect an assistant action to my backend server
I am using my own Oauth server and followed the instructions on
https://developers.google.com/actions/identity/oauth2?oauth=code
I am using actions_intent_Sign_in for my dialogflow event intent (like https://actions-on-google.github.io/actions-on-google-nodejs/classes/conversation_helper.signin.html)
when i use my action to sign in, i get the login window to my server, i do the account linking and i can see that i generated the tokens on my server but i cant find the token in (conv.user.access.token)
and this is the code for my intent using "actions on google sdk "
'use strict';
var _ = require('lodash');
var path = require('path')
var express = require('express')
var http = require('http')
const bodyParser = require('body-parser');
var expressApp = express().use(bodyParser.json());
var server = http.createServer(expressApp).listen(3000)
const {
dialogflow,
SignIn
} = require('actions-on-google');
const app = dialogflow({
debug: true,
clientId: '7b4a6dfc-4b35-11e9-8646-d663bd873d93'
});
app.intent('Start Sign-in', conv => {
conv.ask(new SignIn());
});
app.intent('Get Sign-in', (conv, params, signin) => {
console.log("get sign in ");
console.log(JSON.stringify(signin));
if (signin.status === 'OK') {
const access = conv.user.access.token
console.log("the access token is " + access);
conv.ask('Great, thanks for signing in! What do you want to do next?');
} else {
conv.ask('I wont be able to save your data, but what do you want to do next?.');
}
});
and the response comes back as
{"#type":"type.googleapis.com/google.actions.v2.SignInValue","status":"OK"}
the access token is undefined
Response {
"status": 200,
"headers": {
"content-type": "application/json;charset=utf-8"
},
"body": {
"payload": {
"google": {
"expectUserResponse": true,
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "Great, thanks for signing in! What do you want to do next?"
}
}
]
}
}
}
}
}
the user object of conv has only this data
"user": {
"raw": {
"lastSeen": "2019-03-20T12:46:23Z",
"locale": "en-US",
"userId": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst"
},
"storage": {},
"_id": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst",
"locale": "en-US",
"permissions": [],
"last": {
"seen": "2019-03-20T12:46:23.000Z"
},
"name": {},
"entitlements": [],
"access": {},
"profile": {}
}
i dont know where the access/refresh token can be found or if there is any requirement for the post to send from my oauth server that i missed
so finally i managed to get it working with the help of Actions on Google Support Team
the problem was me having another google account logged-in in another tab, even though i had the AoG and dialogflow agent connected with the same account
tried all using incognito window and it works
Using pact to verify if the response header matches for the consumer and provider.
Running the pact verification on the provider side gives me the following error:
Failure/Error: expect(header_value).to match_header(name, expected_header_value)
Expected header "abc" to equal "xyz", but was nil
However, when I inspect if my response header, it gives me the expected value ("xyz").
Here is the sample pact file I'm trying to verify:
"interactions": [
{
"description": "a request to do something",
"request": {
"method": "get",
"path": "/example"
},
"response": {
"status": 200,
"headers": {
"abc": "xyz"
}
}
}]
I’m new to pact. Any help would be appreciated.
While this is an old post, I hope this will help anyone who views this.
I'm not familiar with ruby, however if your using a basic HTTP Rest request you need to add the accept headers on the 'withRequest' as well as the expected headers on the 'withRespondWith'. You can use Postman to view both request and response headers; JavaScript Example:
describe('When a request is made to get all <resources>', () => {
beforeAll(() =>
provider.setup().then(() => {
provider.addInteraction({
uponReceiving: 'a request to receive to receive all...',
withRequest: {
method: 'GET',
path: '/<resource>',
// Default headers from Axios documentation
headers: { Accept: "application/json, text/plain, */*" }
},
...
willRespondWith: {
// expected headers
headers: { "Content-Type": "application/json; charset=utf-8" },
...
I am trying to create a new issue utilizing the JIRA REST API and whenever I try, I get back the following generic error:
{ errorMessages: [ 'Internal server error' ], errors: {} }
I can successfully GET from the API, and the credentials I'm connecting with have full Admin access to JIRA (so it's not an Auth issue), but I get this error every time with POST. Below is a snippet of the JSON data I'm sending. Am I missing anything obvious?
Below is my JavaScript code. Note I'm using jira-connector from npm. (Real domain replaced with mydomain for this sample code)
const JiraClient = require('jira-connector');
const dotenv = require('dotenv').config();
function createNewIssue(fields) {
const encoded = process.env.JIRA_ENCODED_PW;
const jira = new JiraClient({
host: 'mydomain.atlassian.net',
basic_auth: {
base64: encoded
}
});
return new Promise((resolve, reject) => {
jira.issue.createIssue(fields, (error, issue) => {
if (error) {
console.log(error);
reject(error);
} else {
console.log(issue);
resolve(encoded);
}
});
})
}
Below is the JSON that's being passed into fields in the JS above. Note customfield_17300 is a radio button, and customfield_17300 is a multi-select box. For both cases, I've tried using the "id" and also the actual string "name" value. All IDs below were taken straight from a API GET of the same issue in question:
{
"fields": {
"project": {
"id": "13400"
},
"summary": "TEST API TICKET - 01",
"issuetype": {
"id": "11701"
},
"customfield_14804": { "id": "13716" },
"customfield_14607": "Hardware",
"customfield_17300": [
{
"id": "18322"
}
] ,
"customfield_16301": "Customer PO",
"customfield_14800": "LA, California",
"customfield_16302": "FEDEX 234982347g"
}
}
sigh I figured it out... other posts that said this cryptic error was due to a malformed JSON were correct.
In my route, I passed fields as coming from req.body.fields which actually dove into the fields values instead of passing it straight through. This made it so that when the JSON was sent to JIRA the fields outer wrapper was missing. I changed my route to pass along req.body instead of req.body.fields and all was well.
...that was a fun 4 hours...
While using strongloop loopback I want check the database for accesstoken and username existence in every request.
So i am making a middleware code:
module.exports = function() {
return function xAuth(req, res, next) {
console.log(req);
};
};
I have added it to :initial" middleware json
"initial": {
"compression": {},
"cors": {
"params": {
"origin": true,
"credentials": true,
"maxAge": 86400
}
},
"./middleware/trumptAuth": {},
"helmet#xssFilter": {},
"helmet#frameguard": {
"params": [
"deny"
]
},
"helmet#hsts": {
"params": {
"maxAge": 0,
"includeSubdomains": true
}
},
"helmet#hidePoweredBy": {},
"helmet#ieNoOpen": {},
"helmet#noSniff": {},
"helmet#noCache": {
"enabled": false
}
}
i want to execute an sql query here but i have no idea on how can i do that, I probably just need "app" variable access or directly "dataSource" access.
Any help is appreciated.
From the docs, "Using variables in middleware" (https://docs.strongloop.com/display/LB/Defining+middleware#Definingmiddleware-Usingvariablesinvalues), it looks like you can pass any part of the app object using this syntax: ${var}. The datasource property exists under the core app object, so you should be able to pass it in that way.
To access "app" variable from middleware use
app.use(function(req, res, next) {
var app = req.app;
...
});
To access "dataSource" variable from middleware use
app.use(function(req, res, next) {
var dataSource = app.datasources.db;
...
});
To access an specific model from middleware use
app.use(function(req, res, next) {
var app = req.app;
var modelName = app.model.modelName;
...
});
for more methods/sources use this documentation link
https://docs.strongloop.com/display/public/LB/Working+with+LoopBack+objects