The correct token to use with GraphQl? - amazon-web-services

I'm using the latest official aws plugin and the flutter graphql plugin. I want to use AppSync. And in the graphql readme it describes how to do this.
To use with an AppSync GraphQL API that is authorized with AWS Cognito
User Pools, simply pass the JWT token for your Cognito user session in
to the AuthLink:
// Where `session` is a CognitorUserSession
// from amazon_cognito_identity_dart_2
final token = session.getAccessToken().getJwtToken();
final AuthLink authLink = AuthLink(
getToken: () => token,
);
My problem is from the aws plugin, There is a method called fetchAuthSession that returns a session with different tokens. But it doesn't return the jwtToken.. Are any of the returned tokens used for AppSync? Please click the session link for the different tokens...

The token that you need for AppSync can be got by the Amplify plugin. It's called accessToken
Future<String> getAcessToken() async {
CognitoAuthSession res = await Amplify.Auth.fetchAuthSession(options: CognitoSessionOptions(getAWSCredentials: true));
final accessToken = res.userPoolTokens.accessToken;
return accessToken;
}

Related

Executing AWS Signv4 HTTP API from Javascript returns 403 error

I'm developing a Javascript (browser) client for HTTP API's in AWS API Gateway.The API's use an IAM authorizer. In my Javascript App I log in through a Cognito Identity Pool (developer identity). Next I convert the OpenID token into an access key id, secret access key and session token using AWS.CognitoIdentityCredentials.
I then want to use these credentials to make the API call, using the code below. I see the call being executed, but I get a HTTP/403 error back. The reply does not contain any further indication of the cause. I'd appreciate all help to understand what is going wrong. When disabling the IAM authorizer, the HTTP API works nicely.
I also tried the JWT authorizer, passing the OpenID token received from the Cognito Identity Pool (using http://cognito-identity.amazon.com as provider). When doing so I get the error: Bearer scope="" error="invalid_token" error_description="unable to decode "n" from RSA public key" in the www-authenticate response header.
Thanks a lot.
// Credentials will be available when this function is called.
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
let test_url = 'https://xxxxxxx.execute-api.eu-central-1.amazonaws.com/yyyyyy';
var httpRequest = new AWS.HttpRequest("https://" + test_url, "eu-central-1");
httpRequest.method = "GET";
AWS.config.credentials = {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: sessionToken
}
var v4signer = new AWS.Signers.V4(httpRequest, "execute-api");
v4signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
fetch(httpRequest.endpoint.href , {
method: httpRequest.method,
headers: httpRequest.headers,
//body: httpRequest.body
}).then(function (response) {
if (!response.ok) {
$('body').html("ERROR: " + JSON.stringify(response.blob()));
return;
}
$('body').html("SUCCESS: " + JSON.stringify(response.blob()));
});
After some debugging and searching the web, I found the solution. Generating a correct signature seems to require a 'host' header:
httpRequest.headers.host = 'xxxxxxx.execute-api.eu-central-1.amazonaws.com'
After adding this host header the API call succeeds.

Google function HTTP trigger - authentication problem server to server with service account

What i want to do: To call a google function from my server/machine & limit it usage with a (simple) authentication.
What i use: Node.js, google-auth-library library for authentication.
What have I done/tried:
1) Created a project in Google Cloud Functions
2) Created a simple google function
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(message);
};
3) Set my custom service account
4) Enabled api:
- Cloud Functions API
- IAM Service Account Credentials API
- Cloud Run API
- Compute Engine API
- IAM Service Account Credentials API
5) Given to my server account necessary authorization (project owner, cloud function admin, IAM project admin... (need more?)
6) Generated key from my service account and saved it in json format
NB: with allUser permission (without authorization required), i can call my endpoint without problem
7) From my project i tried to auth my function in this way
const { JWT } = require('google-auth-library');
const fetch = require('node-fetch');
const keys = require('./service-account-keys.json');
async function callFunction(text) {
const url = `https://europe-west1-myFunction.cloudfunctions.net/test`;
const client = new JWT({
email: keys.client_email,
keyFile: keys,
key: keys.private_key,
scopes: [
'https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/iam',
],
});
const res = await client.request({ url });
const tokenInfo = await client.getTokenInfo(client.credentials.access_token);
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${client.credentials.access_token}`,
},
});
if (response.status !== 200) {
console.log(response);
return {};
}
return response.json();
} catch (e) {
console.error(e);
}
}
ℹ️ if i try to pass at client.request() url without name of function (https://europe-west1-myFunction.cloudfunctions.net), i not received error, but when use the JWT token obtained in fetch call, i received the same error.
RESULT:
Error:
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>401 Unauthorized</title>
</head>
<body text=#000000 bgcolor=#ffffff>
<h1>Error: Unauthorized</h1>
<h2>Your client does not have permission to the requested URL <code>/test1</code>.</h2>
<h2></h2>
</body></html>
❓ How do I call a google function with any protection to prevent anyone from using it? (I don't need specific security, just that random users don't use it)
Thanks in advance for any help
When you call a private function (or a private Cloud Run) you have to use a google signed identity token.
In your code, you use an access token
headers: {
Authorization: `Bearer ${client.credentials.access_token}`,
},
Access token work when you have to request Google Cloud API, not your services
And the google signed is important, because you can easily generate a self signed identity token with the google auth lib, but it won't work
You have code sample here and I wrote a tool in Go if you want to have a try on it
** EDIT **
I worked on an example, and, even if I never liked Javascript, I have to admit that I'm jealous!! It's so simple in Node!!
Here my working example
const {GoogleAuth} = require('google-auth-library');
async function main() {
// Define your URL, here with Cloud Run but the security is exactly the same with Cloud Functions (same underlying infrastructure)
const url = "https://go111-vqg64v3fcq-uc.a.run.app"
// Here I use the default credential, not an explicit key like you
const auth = new GoogleAuth();
//Example with the key file, not recommended on GCP environment.
//const auth = new GoogleAuth({keyFilename:"/path/to/key.json"})
//Create your client with an Identity token.
const client = await auth.getIdTokenClient(url);
const res = await client.request({url});
console.log(res.data);
}
main().catch(console.error);
Note: Only Service account can generate and identity token with audience. If you are in your local computer, don't use your user account with the default credential mode.

How to use only `ctx.identity.cognitoIdentityId` to get related Cognito User Pool user data in AppSync Resolver?

Earlier, when we started our project only with Cognito User Pool I created a lot of resolvers with validation by Cognito User Pool data, for example:
#if( $ctx.identity.claims["custom:role"] == "admin" )
...some code...(get data, invoke lambda, e.t.c.)
#else
$utils.unauthorized()
#end
But later we needed other authorization providers (Facebook, Google e.t.c.). Therefore, we migrated to cognitoIdentityId, but there was a problem obtaining user data from the Cognito User Pool in the AppSync resolvers.
In AWS Lambda I found Cognito User Pool id by the cognitoIdentityAuthProvider and can get Cognito User Attributes as UserAttributes see code below:
...
...
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider({
apiVersion: '2016-04-18',
});
const getCognitoUserPoolId = (authProvider) => {
const parts = authProvider.split(':');
return parts[parts.length - 1].slice(0, -1);
};
// cognitoIdentityAuthProvider, which we pass as an parameter($ctx.identity.cognitoIdentityAuthProvider) from the AppSync resolver
const SUB = getCognitoUserPoolId(cognitoIdentityAuthProvider);
const params = {
UserPoolId: COGNITO_USER_POOL_ID,
Username: SUB,
};
try {
const { UserAttributes } = await cognitoidentityserviceprovider.adminGetUser(params).promise();
...
...
} catch (error) {
return error;
}
The question is how to get data from Cognito User Pool using cognitoIdentityId in AppSync resolvers? Or are there any other options? Hope I do not have to create a separate lambda for each resolver?
I assume you are using AWS_IAM as the authorization type on your GraphQL API and you are federating a cognito user pool user through Cognito Federated Identities to obtain temporary AWS credentials that you use to call your GraphQL API.
At the moment, the federating user information is not available in the $context.identity object. The workaround for this is what you posted to retrieve it using a lambda and use it further in your resolver by using pipeline resolvers for example.
I am on the AppSync team and we have heard this feature request in the past so I will +1 it for you on your behalf.

How to validate non authenticated users from a mobile app on a server?

My goal : Have a mobile app that does not require users to ever sign in. Have these unauthenticated users hit my server.
What I have : My server is using the AWS API Gateway / AWS Lambda setup. The custom authorizer I used for AWS API Gateway was designed using this example. I Also pasted the code from this example below (A).
My Question : From the code block below (A), I get the impression I should use JWT. How can I use JWT to validate unauthenticated users when these tokens expire? If JWT is not the best thing to use, what would be?
Thanks!
(A)
var nJwt = require('njwt');
var AWS = require('aws-sdk');
var signingKey = "CiCnRmG+t+ BASE 64 ENCODED ENCRYPTED SIGNING KEY Mk=";
exports.handler = function(event, context) {
console.log('Client token: ' + event.authorizationToken);
console.log('Method ARN: ' + event.methodArn);
var kms = new AWS.KMS();
var decryptionParams = {
CiphertextBlob : new Buffer(signingKey, 'base64')
}
kms.decrypt(decryptionParams, function(err, data) {
if (err) {
console.log(err, err.stack);
context.fail("Unable to load encryption key");
} else {
key = data.Plaintext;
try {
verifiedJwt = nJwt.verify(event.authorizationToken, key);
console.log(verifiedJwt);
// parse the ARN from the incoming event
var apiOptions = {};
var tmp = event.methodArn.split(':');
var apiGatewayArnTmp = tmp[5].split('/');
var awsAccountId = tmp[4];
apiOptions.region = tmp[3];
apiOptions.restApiId = apiGatewayArnTmp[0];
apiOptions.stage = apiGatewayArnTmp[1];
policy = new AuthPolicy(verifiedJwt.body.sub, awsAccountId, apiOptions);
if (verifiedJwt.body.scope.indexOf("admins") > -1) {
policy.allowAllMethods();
} else {
policy.allowMethod(AuthPolicy.HttpVerb.GET, "*");
policy.allowMethod(AuthPolicy.HttpVerb.POST, "/users/" + verifiedJwt.body.sub);
}
context.succeed(policy.build());
} catch (ex) {
console.log(ex, ex.stack);
context.fail("Unauthorized");
}
}
});
};
My Question : From the code block below (A), I get the impression I
should use JWT. How can I use JWT to validate unauthenticated users
when these tokens expire? If JWT is not the best thing to use, what
would be?
Why do you need any validation at all? If you want to allow unauthenticated access to your API you should just do that and save yourself some effort.
Update
If you want to restrict access and not require a user to login, you may want to consider simply using Cognito Identity. It supports unauthenticated identities and migration to authenticated access either via Cognito Your User Pools or another federated provider. It is worth noting that if you go this route, you will want to secure/obscure your identity pool id properly within your app to minimize the chance of it being extracted.
Using Cognito Identity, you would be able to get AWS credentials to sign the requests to your API using standard Signature version 4 and avoid the cost and overhead of managing a custom authorizer.

Signing with temporary credentials for API gateway request problems

I'm trying to invoke my API Gateway with authenticated users with the REST API. For this I'm using: Cognito UserPool + Cognito Identity Pool + API Gateway + AWS_IAM Authorization + Cognito Credentials. From what I've gathered I need to sign my request with (temporary credentials). Based on this thread I want to sign my request with the following keys:
{
SecretKey: '',
AccesKeyId: '',
SessionKey: ''
}
If I use an associated user from my IAM console and use the corresponding SecretKey + AccesKeyID everything works fine. However, I want to use the Unauthenticated and Authenticated roles from my Identity Pools to apply IAM policies based on authenticated or unauthenticated users.
FYI: I can call the authenticated functions from this part of the documentation.
I'm building a React-Native app, and because of that I want to keep the native SDK to a minimum and I'm only using AWSCognitoIdentityProvider part. For the user handling.
I trying to receive the correct keys using this Objective-C code:
[[self.credentialsProvider credentials] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
}
else {
AWSCredentials *response = task.result;
NSString *accessKey = response.accessKey;
NSString *secretKey = response.secretKey;
NSString *sessionKey = response.sessionKey;
NSDictionary *responseData = #{
#"AccessKey" : accessKey,
#"SecretKey" : secretKey,
#"SessionKey": sessionKey
};
}
return nil;
}];
The rest I've setup using the relevant docs.
I (wrongly?) tried to sign my requests with the
AccessKey, SecretKey, SessionKey retrieved from the CredentialsProvider above.
{
SecretKey: credentials.SecretKey,
AccesKeyId: credentials.AccessKey,
SessionKey: credentials.SessionKey
}
The signing fails with the following error:
{ message: 'The security token included in the request is invalid.' }
So the question I have is: which keys should I use to sign my requests for authenticated users so that I can apply the attached IAM policies from my Cognito Setup?
Thanks for any help :)
Like Michael - sqlbot, point out, it should be SessionToken instead of SessionKey. I found a better instruction on how to get credentials from Cognito.