I am dealing with some legacy applications and want to use Amazon AWS API Gateway to mitigate some of the drawbacks.
Application A, is able to call URLs with parameters, but does not support HTTP basic AUTH. Like this:
https://example.com/api?param1=xxx¶m2=yyy
Application B is able to handle these calls and respond. BUT application B needs HTTP basic authentication.
The question is now, can I use Amazon AWS API Gateway to mitigate this?
The idea is to create an API of this style:
http://amazon-aws-api.example.com/api?authcode=aaaa¶m1=xxx¶m2=yyy
Then Amazon should check if the authcode is correct and then call the API from Application A with all remaining parameters while using some stored username+password. The result should just be passed along back to Application B.
I could also give username + password as a parameter, but I guess using a long authcode and storing the rather short password at Amazon is more secure. One could also use a changing authcode like the ones used in 2-factor authentications.
Path to a solution:
I created the following AWS Lambda function based on the HTTPS template:
'use strict';
const https = require('https');
exports.handler = (event, context, callback) => {
const req = https.get(event, (res) => {
let body = '';
res.setEncoding('utf8');
res.on('data', (chunk) => body += chunk);
res.on('end', () => callback(null, body));
});
req.on('error', callback);
req.end();
};
If I use the Test function and provide it with this event it works as expected:
{
"hostname": "example.com",
"path": "/api?param1=xxx¶m2=yyy",
"auth": "user:password"
}
I suppose the best way from here is to use the API gateway to provide an interface like:
https://amazon-aws-api.example.com/api?user=user&pass=pass¶m1=xxx¶m2=yyy
Since the params of an HTTPs request are encrypted and they are not stored in Lambda, this method should be pretty secure.
The question is now, how to connect the API gateway to the Lambda.
You can achieve the scenario mentioned with AWS API Gateway. However it won't be just a proxy integration, rather you need to have a Lambda function which will forward the request by doing the transformation.
If the credentials are fixed credentials to invoke the API, then you can use the environmental variables in Lambda to store them, encrypted by using AWS KMS Keys.
However if the credentials are sent for each user (e.g logged into the application from a web browser) the drawbacks of this approach is that you need to store username and password while also retrieving it. Its not encourage to store passwords even encrypted. If this is the case, its better to passthrough (Also doing the transformations) rather storing and retrieving in between.
Related
I have a React app built using Serverless NextJS and served behind AWS CloudFront. I am also using AWS Cognito to do authentication of our users.
After a user successfully authenticates through AWS Cognito, they are redirected to my React App with a query string containing OAuth tokens (id_token, access_token, refresh_token, raw[id_token], raw[access_token], raw[refresh_token], raw[expires_in], raw[token_type]).
It seems that the query string is simply larger than AWS CloudFront's limits and it is throwing the following error below:
413 ERROR
The request could not be satisfied.
Bad request. We can't connect to the server for this app...
Generated by cloudfront (CloudFront)
Request ID: FlfDp8raw80pAFCvu3g7VEb_IRYbhHoHBkOEQxYyOTWMsNlRjTA7FQ==
This error has been encountered before by many other users (see example). Keen to know:
Are there any workarounds? Perhaps is there a way to configure AWS Cognito to reduce the number of tokens that it is passing in the query string by default?
Is it possible to configure AWS CloudFront to ignore enforcing its default limits on certain pages (and not cache theme)?
What's the suggestion going forward? The only thing I can imagine is not to use AWS CloudFront.
After analysing the query fields that AWS Cognito sends to a callback URL, I was able to determine that not all fields are required for my usecase. Particularly the raw OAuth token fields.
With that information, I solved the problem by writing a "middleware" to intercept my backend system redirecting to my frontend (that is sitting behind CloudFront) and trimming away query string fields that I do not need to complete authentication.
In case this could inspire someone else stuck with a similar problem, here is what my middleware looks like for my backend system (Strapi):
module.exports = (strapi) => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
await next();
if (ctx.request.url.startsWith("/connect/cognito/callback?code=")) {
// Parse URL (with OAuth query string) Strapi is redirecting to
const location = ctx.response.header.location;
const { protocol, host, pathname, query } = url.parse(location);
// Parse OAuth query string and remove redundant (and bloated) `raw` fields
const queryObject = qs.parse(query);
const trimmedQueryObject = _.omit(queryObject, "raw");
// Reconstruct original redirect Url with shortened query string params
const newLocation = `${protocol}//${host}${pathname}?${qs.stringify(
trimmedQueryObject
)}`;
ctx.redirect(newLocation);
}
});
},
};
};
My code uses the AWS Javascript SDK to upload to S3 directly from a browser. Before the upload happens, my server sends it a value to use for 'Authorization'.
But I see no way in the AWS.S3.upload() method where I can add this header.
I know that underneath the .upload() method, AWS.S3.ManagedUpload is used but that likewise doesn't seem to return a Request object anywhere for me to add the header.
It works successfully in my dev environment when I hardcode my credentials in the S3() object, but I can't do that in production.
How can I get the Authorization header into the upload() call?
Client Side
this posts explains how to post from a html form with a pre-generated signature
How do you upload files directly to S3 over SSL?
Server Side
When you initialise the S3, you can pass the access key and secret.
const s3 = new AWS.S3({
apiVersion: '2006-03-01',
accessKeyId: '[value]',
secretAccessKey: '[value]'
});
const params = {};
s3.upload(params, function (err, data) {
console.log(err, data);
});
Reference: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html
Alternatively if you are running this code inside AWS services such as EC2, Lambda, ECS etc, you can assign a IAM role to the service that you are using. The permissions can be assigned to the IAM Role
I suggest that you use presigned urls.
I've just started implementing Authentication in my Web API.
I want to start with Basic Authentication
I learned that i've to pass Username and Password in every request.
So, lets say i'm doing some Admin task and making API call for same like this:
$.ajax({
url: host + "homework/delete/" + $(this).data("id"),
type: 'DELETE',
headers:
{
Authorization: 'Basic ' + btoa(username + ':' + password)
},
success: function (d) {
$tr.remove();
},
error: function () {
alert("Error please try again");
}
});
So, although my username/password is in variable, but their value must be at page(source). whosoever access that page, can see those credentials.
That means, whosoever get to know the url of that page, can see the credentials.
If i put a login page, how should i check on admin page that this user is authenticated. Should i use Cookies? to set something if user is coming through login page?
To enhance security, I think there should be another approach. At first you need to authenticate to you service using username and password, and receive authentication token with limited lifetime, then you should use this token to access your services.
I think you have to choose another approach:
create a server side application with UI (PHP, Java, ...)
this application has a session management
the credentials are stored in the configuration of the server side app
the requests to the service which is secured by Basic Authentication are performed by the server app. The responses are delivered to the client
You can't hide the credentials if you are creating a client side JavaScript application. Another issue with your approach maybe this: does the secured service support CORS (cross origin resource sharing) ?
How to properly handle authentication in GraphQL servers?
Is it ok to pass a JWT token at the Authorization header of query/mutation requests?
Should I use something from GraphQL specification?
Stateless solutions is preferable.
Thanks.
A while ago I was wondering the same thing for sometime,
but apparently authentication is out of the scope of what GraphQL is trying to accomplish (see the conversations on Github).
But there are solutions such as this which handles it with sessions.
Assuming you use express-graphql, here is what you can do.
import graphQLHTTP from 'express-graphql'
app.use(`/graphql`, [aValidationFunction, graphQLHTTP(options)])
function aValidationFunction(req, res, next) {
const { authorization } = req.headers
// Do your validation here by using redis or whatever
if (validUser) {
return next()
} else {
return res.status(403)
}
}
It depends on whether your GraphQL consumer is a webapp or mobileapp.
If it is a webapp, then I would recommend sticking with session-cookie-based authentication since most popular web frameworks support this, and you also get CSRF protection.
If it is a mobileapp, then you will want JWT. You can try manually getting a cookie header from login response, and put stuff this "cookie" in your next request, but I had problem that some proxy servers strip off this "cookie", leaving your request unauthenticated. So as you said, including JWT in every authenticated request (GraphQL request) is the way to go.
I have set up a servicestack service with basic authentication using the first example, here:
https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization
This automatically sets up a route: /auth/basic
However, I cannot find any information or examples on how to format a request to this URL (Variables/GET/POST/Auth Header, etc.).
I am able to access a simple service using the basic authentication credentials, so they are active and correct.
I have no custom authentication plugged in, just basic authentication.
I have tried:
Using a JsonServiceClient to send UserName and Password variables by GET or Json POST to /auth/basic, with and without an Auth header also containing the user & pass.
Using a browser to send GET requests with URL parameters of the user/pass, or as http://user:pass#localhost:123/auth/basic
I always just get "HTTP/1.1 401 Invalid BasicAuth credentials".
The only examples I can find involve some kind of custom authentication, and then /auth/credentials is accessed, but I want to use /auth/basic
I have looked at the code and it looks like it reads an Auth header, but the service does not accept one.
I am actually trying to get this working so I can then disable it and verify it is disabled (I want to require basic authentication for every request).
Questions are:
What is the correct way to call the /auth/basic service? I will take a servicestack client API example, specifications or even a raw http request!
How do you disable the /auth services altogether?
Many thanks.
What is the correct way to call the /auth/basic service? I will take a servicestack client API example, specifications or even a raw http request!
var client = new JsonServiceClient("http://localhost:56006/api");
var resp = client.Post(new Auth() { UserName = "TestUser", Password = "Password" });
This assumes you have also registered an ICacheClient and IAuthUserRepository (and added a user account)
The JSON format looks like this if you call into /auth/basic?format=json
{
"UserName": "admin",
"Password": "test"
"RememberMe": true
}
How do you disable the /auth services altogether?
Don't add the AuthFeature plugin to configuration.
You can also remove plugins
Plugins.RemoveAll(x => x is AuthFeature);
Putting the following in apphost config seems to do the trick.
//Disable most things, including SOAP support, /auth and /metadata routes
SetConfig(new EndpointHostConfig()
{
EnableFeatures = Feature.Json | Feature.Xml
});
I am a little suspicious about what this does to /auth however, because it returns an empty response, while most routes return 404.
So, would this truly disable the /auth functionality? As in, if someone formed a correct request to /auth/credentials, will it still return an empty response?