We're working on developing a microservice based architecture employing Google Cloud Functions.
We've developed a few functions and want to implement a discovery service. This discovery service would be used to determine if a specific function exists and is operational.
The service discovery itself is a cloud function. It makes a rest request to the below API and succeeds in local development using functions emulator and the default application credentials.
Google provides an API for this [https://cloud.google.com/functions/docs/reference/rest/v1beta2/projects.locations.functions/get][1]
When deployed to production we're receiving:
{ "code": 401, "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "status": "UNAUTHENTICATED" } }
Cloud functions are stateless so there's no option to use a service account that I can see. How do I go about authenticating a cloud function to call the functions api to determine if a function is available?
Below is how we've accomplished this in a local dev environment:
var options = {
method: 'get',
uri: `https://cloudfunctions.googleapis.com/v1beta2/projects/${config.PROJECT_ID}/locations/${config.LOCATION_ID}/functions/${functionName}`
}
console.log (options.uri);
request(options, function (err, res, body) {
if (!err && res.status === 200) {
if(typeof res.body.httpsTrigger.url !== undefined) {
console.log('found a function');
return cb(false, res.body.httpsTrigger.url);
}
else {
console.log('no function found, looking for static content');
return cb(true, `Service doesn't exist, returned ${res.status}`)
}
}
else {
console.log('no function found, looking for static content');
return cb(true, `Service doesn't exist, returned ${res.status}`);
}
});
So I finally figured out how to do this. It's a bit of hack:.
Download the JSON Key file for the Service Account.
Add the JSON file to the Function source.
Install the google-auth-library NPM module.
Modify the request to use the client from google-auth-library
const keys = require('./VirtualAssistantCred.json');
const {auth} = require('google-auth-library');
const client = auth.fromJSON(keys);
client.scopes = ['https://www.googleapis.com/auth/cloud-platform'];
client.authorize().then(function () {
const url = https://cloudfunctions.googleapis.com/v1beta2/projects/${config.PROJECT_ID}/locations/${config.LOCATION_ID}/functions/${functionName};
client.request({url}, function(err, res) {
if (err) {
console.log(Error: ${err});
return cb(true, Service, ${functionName} doesn't exist, returned ${res})
} else {
console.log(RESPONSE: ${JSON.stringify(res.data)});
console.log(Found Service at ${res.data.httpsTrigger.url});
return cb(false, res.data.httpsTrigger.url);
}
});
}
);
The API you mention contains method: projects.locations.functions.get , that requires one of the following OAuth scopes:
https://www.googleapis.com/auth/cloudfunctions
https://www.googleapis.com/auth/cloud-platform
You may have a look at the Authentication in HTTP Cloud Functions online document.
Related
I set up my first API Gateway in AWS and trying to access the API from A Express route. This gives the following error: {"message":"Missing Authentication Token"}.
When accessing the exact same API through Postman it works fine!
Code is as followed:
router.get(
'/users',
async (req, res) => {
console.log(`${API_BASE_URL}/users}`);
try {
const apiRes = await needle('post', `${API_BASE_URL}/users}`)
const data = apiRes.body;
res.status(200).json(data);
} catch(e) {
res.status(500).json({error})
}
}
);
The logged url works fine as a post request in Postman.
Completely missed the extra } in ${API_BASE_URL}/users}
My project is react native app that uses aws amplify and a rest api through api gateway. i am trying to add an item into my dynamoDB table by calling an API request like so :
const createItemDB = async () => {
API.post("nutritionAPI", "/items", {
body: {
userID: "authenticated user 1 ",
dateID: "1-28-2023",
},
headers: {
Authorization: `Bearer ${(await Auth.currentSession())
.getIdToken()
.getJwtToken()}`,
},
})
};
But this gets the error "request failed with 403 at node_modules\axios\lib\helpers\cookies.js:null in write" Prior to calling createItemDB, i authenticated myself as an "end-user" by Auth.signIn and each time i restart the app, I am still authenticated through my function below
const loadApp = async () =>{
await Auth.currentAuthenticatedUser()
.then(user=>{
console.log("successfully authd" + user)
})
.catch(()=>{
console.log("error signing in")
})
}
So I know i am authenticated. Im new to aws services, and im unsure how to configure the api gateway correctly. the only way to add an item to dynamodb is if I remove the header in my createItemDB call which means any unauthenticated user can add to my dynamodb? I tried to enable CORS inside api gateway but it still doesnt work
I'd like to create a middleware that checks the authorization header, decodes the token and sends the decoded data to the actual function, just like you would by adding userData to the request and using next() on an Express server, so the actual function gets back the decoded data on the req and it can then check what content to display to the user (if any).
I'm using Lambda functions on a serverless framework.
This was the function on my Express NodeJS local server:
const authorizerFunc = async (req, res, next) => {
let token;
try {
if (
req.headers.authorization &&
req.headers.authorization.split(" ")[0] === "Bearer"
) {
token = req.headers.authorization.split(" ")[1];
}
if (!token) {
req.userData = { userId: "", username: "" };
next();
return;
}
const decodedToken = jwt.verify(token, process.env.JWT_SECRET_KEY);
console.log("DECODED TOKEN", decodedToken);
req.userData = {
userId: decodedToken.userId,
username: decodedToken.username,
email: decodedToken.email,
};
next();
} catch (err) {
req.userData = { userId: "", username: "" };
next();
return;
}
};
The question is, how do I create a Lambda function that does this and sends the decoded data to the real function?
Edit: is it bad if I decode the auth token directly in the functions at the very beginning? I don't think it would add huge complexity to them.
Well, I don't have an actuall example for the serverless framework, but i can tell what you should do.
Create an Lambda Function to act as a Amazon API Gateway Lambda authorizer - you can see the documentation here - https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
make sure you do the validation logic what you have defined, and also return the context object in the response - which you can define your user data
add the Amazon API Gateway Lambda authorizer to the API Gateway - https://docs.aws.amazon.com/apigateway/latest/developerguide/configure-api-gateway-lambda-authorization-with-console.html
If the authorization successful your rest api lambda can access the context object with the user data, which you customize in step 2
I am using AWS Cognito for Authentication using user pools and I have all my APIs configured on the API gateway. I directly hit cognito from the Angular client, store the tokens returned by Cognito in local storage and use them in subsequent calls.
The problem however is, if the token I send from Angular has expired the Cognito authentication fails and no Integration backend is hit in this case. As a result, I am getting a 401 error in Chrome.
The interesting thing however is that this 401 code is not available to me in the HTTP response object that is passed to Angular. A default 0 code is received by the Angular and this seems to be the case with all the error code received from server (either cognito or backend).
I tried to explore around and found that the issue might be because the gateway is not sending proper CORS headers in the error cases. I have read following related docs but unfortunately I couldn't find out a way to resolve the issue.
Can someone suggest a solution to this.
Edit: I also read somewhere that it is a known AWS issue. Is that the case ?
You can manage the Cognito session before making the call to the API Gateway.
In the example below, the getSession method has a callback that prints out any error messages to the console, and returns.
////// Cognito.js
import {
CognitoUserPool,
CookieStorage
} from 'amazon-cognito-identity-js'
import config from '../config'
const cookieSettings = {
domain: '.xxxxxxxxxxx.com',
secure: true
}
export default {
cookieSettings,
pool: new CognitoUserPool({
UserPoolId: config.UserPoolId,
ClientId: config.ClientId,
Storage: new CookieStorage(cookieSettings)
})
}
//////
////// actions.js
import Cognito from './Cognito'
export function fetchUser() {
// get cognito user cookies
const cognitoUser = Cognito.pool.getCurrentUser()
if (cognitoUser != null) {
// try to get session from cognito
cognitoUser.getSession(function(err, session) {
if (err) {
console.log(err)
return;
}
fetch('https://api-gateway-url', {
headers: {
Authorization: 'Bearer ' + session.getAccessToken().getJwtToken(),
}
})
.then(response => response.json())
.then(json => console.log(json))
})
}
}
I'm pretty new to sails, but after read the doc and followed some examples at the Internet, I decided to give it a shot ;)
I have made an APP that depend on a REST webservice that I want to build in Sails Framework - but after a lots of research I haven't found the right solutions in sails yet.
I think I want to pass a (username, password) or a api_key in each webservice call made from the app?
All the examples that i found was only with a session login method - not with an API key in each call.
I used this tutorial - http://jethrokuan.github.io/2013/12/19/Using-Passport-With-Sails-JS.html
But only logins at post to login page - I want it to login in every call and still want to use the build in REST API blueprints.
The problem in my solution is that a call to like this - will not give me all the users as expected because of the default REST method - I want it to auth the user and give me the result ..
http://example.com:1337/user/?username=test&password=xxx
What is the "best practises" for building a APP with a REST webservice backend? - "with sails"
Some of my auth code:
// policies/authentication.js
if(req.param('username') && req.param('password')) {
UserAuth.auth(req, res, function(err, user) {
if (err) return res.forbidden('You are not permitted to perform this action.');
if(user) {
return next();
}
});
}else{
return res.forbidden('You are not permitted to perform this action.');
}
// services/UserAuth.js
module.exports = {
auth : function(req, res, cb) {
var bcrypt = require('bcrypt');
var passport = require("passport");
passport.authenticate('local', function(err, user, info){
if (err) return cb({ error: 'auth error!', status: 400 });
if(user) {
cb(null, user);
}
})(req, res);
}
}
// config/policies.js
module.exports.policies = {
'*': "authentication"
};
First off, it's bad practice to continuously expose usernames and passwords in the wild like this. At the very least, you should consider issuing access_tokens that expire after some time, and need to be re-issued via a login system.
Second, if you want to authenticate on every request (instead of using sessions), it's better to do so using a request header, rather than putting the credentials in the query string. This is especially true when using Sails blueprints; otherwise you'll have to do extra work to keep the blueprints from using your credentials as search criteria.
When using a header, per-request authorization becomes simple with Sails. Set up a policy in api/policies called (for example) auth.js:
module.exports = function (req, res, next) {
// Find an access header
var accessToken = req.header('my-auth-header');
// No header, no access
if (!accessToken) {return res.forbidden();}
// Find the user with that token
User.findOne({accessToken: accessToken})
.exec(function(err, user) {
// Handle error
if (err) {return next(err);}
// Handle bad access token
if (!user) {return res.forbidden();}
// Handle success
return next();
});
}
Then you can set any controller actions that need authentication using the config/policies.js file:
module.exports = {
SomeController: {
'*': 'auth'
},
...etc...
}