Is there yet an idiomatic way to make native calls to AWS Lambda or API Gateway from a Flutter application? - flutter

Goal: Make a signed request (SigV4) to AWS Lambda or API Gateway from a Flutter application (iOS, for the sake of this question).
For context, AWS introduced support for "native calls to AWS backends in [...] Flutter or Dart applications" back in May of 2022. There is an example of how to sign a request to Cognito to gather information about a User Pool, but I have not seen any application of this concept for Lambda or API Gateway calls, yet.
I'm wondering if anyone else has had success using AWS's official Dart packages to send signed requests or knows of another way to securely call AWS from a Flutter application.
EDIT:
I was able to accomplish the goal. Here's how I did it:
The code (all-caps denotes placeholders for your own values):
import 'dart:convert';
import 'package:aws_common/aws_common.dart';
import 'package:aws_signature_v4/aws_signature_v4.dart';
const signer = AWSSigV4Signer(
credentialsProvider: AWSCredentialsProvider.dartEnvironment(),
);
const region = 'REGION';
Future<void> yourFunction() async {
final scope = AWSCredentialScope(
region: region,
service: AWSService.apiGatewayV2,
dateTime: AWSDateTime(DateTime.now()),
);
final request = AWSHttpRequest(
method: AWSHttpMethod.post,
uri: Uri.https('HOST.execute-api.REGION.amazonaws.com','/STAGE_NAME'),
headers: const {
AWSHeaders.contentType: 'application/x-amz-json-1.1',
},
body: json.encode({
'EVENT_KEY':'EVENT_VALUE',
}).codeUnits,
);
final signedRequest = await signer.sign(
request,
credentialScope: scope,
);
final resp = await signedRequest.send();
final respBody = await resp.decodeBody();
print('\n\n${signedRequest.headers}\n\n${respBody}\n\n${resp.statusCode}\n\n');
}
Within a single AWS region (except where n/a):
create IAM user with execute-api:Invoke permission and programmatic access; store keys securely for later use.
create a Lambda function (can be the default, for testing).
create API in API Gateway:
REST (not private)
Regional endpoint
Add method (for me, POST)
IAM authorization type
Integration type is Lambda
select the target Lambda function, but
do not use Lambda proxy
deploy the newly created API to a new stage (give it a name)
Edit your dart file to include the new resources, including the stage name.
Test your API within API Gateway (I was getting 502 until I unchecked "Lambda proxy integration").
Run the following in your terminal after a successful API test; be sure to insert the keys for the IAM user you created.
flutter run --dart-define=AWS_ACCESS_KEY_ID=... --dart-define=AWS_SECRET_ACCESS_KEY=...
Summary:
In my case, I have a button that executes this function. If you keep the print statement in the above dart code, you should hopefully see {"statusCode": 200, "body": "\"Hello from Lambda!\""} as the response body.
Hope this helps others. Cannot make any guarantees that my approach will work in another environment. I also may have forgotten to include something relevant in the steps above. Still open to questions and suggestions.
Thank you.

Related

How to solve the {code: UNAUTHENTICATED, details: null, message: UNAUTHENTICATED}) error using Google Cloud Functions?

I am currently facing the above mentioned error when using the cloud_functions dependency on my Flutter app. My function https call is as follows:
final HttpsCallable callable = CloudFunctions(region: "region name").getHttpsCallable(functionName: 'function-name')..timeout = const Duration(seconds:30);
And my function invocation within the code is as given below:
onPressed: () async {
try {
dynamic resp = await callable.call(<String, dynamic>{
'message':'hello!',
'url': urlController.text,
});
setState(() {
imgurl = resp.data['image'];
time = resp.data['timestamp'];
});
I have added the ID I am using for authentication to my function via the console IAM. Unfortunately, I still keep receiving the following error:
PlatformException(functionsError, Cloud function failed with exception., {code: UNAUTHENTICATED, details: null, message: UNAUTHENTICATED})
How can I resolve that?
Thanks for your help in advance.
If you have deployed your function in "private mode", I mean, allow only authenticated user, you have to add a valid identity_token in the header of your request.
You have an example here, mostly on end user because it's your use case. Don't use a service account key file because your flutter app is public and you will share your secret publicly.
You can also use Cloud Endpoint with Firebase authentication mode. I wrote an article for setting up an authentication with API key. Simply update the authentication mode and it will works.

Flutter call http requests from a unit\widget test without mocking

I have an app that follows MVC+service architecture. The service layer makes the http requests for rest APIs. However the response of the http requests change intermittently which cause my models to change or else random crashes in my app. SO to capture the change in these APIs I want to write some automated tests which can tell me exactly what changed. A sample test case is as following:
test("login_valid", () async {
final loginData = LoginData(
email: "abc#gmail.com",
password: "123"
);
final parameters = loginData.toJson();
var json = await httpService.post(parameters);
var loginResponse = LoginResponse.fromJson(json);
expect(loginResponse.status, "OK");
});
However, the above code throws SocketException upon run. I know this exception is thrown when INTERNET permission is not given in AndroidManifest.xml but I don't know how to set this for unit\widget tests.
P.S. I can't mock the service layer using mockit or similar framework because the whole point is to test my service layer which doesn't have any business logic but just provides network integration.
Any solution or suggestion will be really helpful. I am okay with other approaches to achieve the same intent also, if there are any.
Check this or the gihub issue discussion pointed there
https://timm.preetz.name/articles/http-request-flutter-test

make meteor restful api/web-service

I have created a new url/route in my app where I need to write a web-service. I need to write a service that deletes user according to the parameters passed in the service. For now, anyone should be able to call that service (will make it secure at later stage). App is built on meteor.
My url is : loaclhost:3000/deleteUser. Now one should be able to call my delete user function defined on this page and pass json structure data as an argument to it. If the data is valid, then the user should be deleted.
Using simple:rest package
Meteor.publish("delUser", function (a, b) {
UserDetails.remove({}); //delete user according to data received
}, {
url: "/testing/delUser", //url where third party will call the function
getArgsFromRequest: function (request) {
// Let's say we want this function to accept a form-encoded request
// with fields named `a` and `b`.
console.log('received : ' + JSON.stringify(request.body) );
var content = request.body;
// Since form enconding doesn't distinguish numbers and strings, we need
// to parse it manually
return [content.a, content.b];
}
})
How to access the function, delUser from a thrid party? I also need to add authentication at a later stage.
Personnally, I use this :
simple:rest
simple:json-routes
simple:rest-accounts-password
I find it easier to implement.
even iron:router comes with server side routes where you can build your own functions and api calls.
http://iron-meteor.github.io/iron-router/#restful-routes
Sample (Server side code) :
Router.map(function () {
this.route("api", {path: "/api/:paramsYouNeed",
where: "server",
action: function(){
this.response.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
if (this.request.method == 'POST') {
var response;
//do whatever you want to do
this.response.end(response);
}
}
});
The other user can call this by making a http.post request to the above url (http:www.a****a.com/api/params)
The easiest way to do this is use the restivus package.
https://atmospherejs.com/nimble/restivus
Restivus makes building REST APIs in Meteor 0.9.0+ easier than ever
before! The package is inspired by RestStop2 and Collection API, and
is built on top of Simple JSON Routes to provide:
A simple interface for creating REST APIs
Easy setup of CRUD endpoints for Mongo Collections
User authentication via the API
Optional login and logout endpoints
Access to this.user in authenticated endpoints
Custom authentication if needed
Role permissions for limiting access to specific endpoints
Works alongside the alanning:roles package - Meteor's accepted role permission package

Firebase: Authenticate an existing user using REST API and Firebases hidden Auth URL

For the past 3 years we have used HTML/Js only with Firebase but now we are using Unity as well.
The current Unity/Firebase only works on Android/iOS when deployed and 99% of our work is on the windows store.
I've actually got a pretty decent Unity/Firebase codebase going but it requires me to use a full App Secret.
All the other libraries expose a method to login with Email/Password but the REST API only allows the use of a token or your app secret that it then states is ill advised to put into your client; I guess the thinking is if you're using a different library that you'll have your own auth/user method which we don't...
Now, I've pulled apart the web version and got this:
https://auth.firebase.com/v2/<myfirebase>/auth/password?&email=dennis%40<mysite>&password=<mypassword>v=js-2.2.9&transport=json&suppress_status_codes=true
So there IS an endpoint that I can send stuff to and I've tested it inside unity with good results.
Obviously the URL isn't guaranteed to stay working but I'm wondering if there is any reason NOT to use this?
Also, Why not just expose this endpoint in the official REST API?
As I understand it, that URL will continue to work for your Legacy Firebase project. You will have to do the same sort of reverse engineering if you want to update to the new Firebase 3.0 API. However, if you are still using a legacy Firebase project -- I encourage you to take a look at this. It has not been updated to work with Firebase 3.0 -- so I needed to do something similar to what you did to allow login to the new API.
I was able to do this with the new API using C# as follows (where FirebaseManager is a Singleton I wrote for Global variables and functions to write and read from/to the DB :
Hashtable loginData = new Hashtable();
loginData.Add ("email", <EMAIL-GOES-HERE>);
loginData.Add ("password", <PASSWORD-GOES-HERE>);
loginData.Add ("returnSecureToken", true);
UnityHTTP.Request loginRequest = new UnityHTTP.Request ("post",
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key="
+ <YOUR-PROJECT-API-KEY-GOES-HERE>, loginData);
loginRequest.Send ((request) => {
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error logging in. Server returned null or malformed response");
}
FirebaseManager.Instance.idToken = (string)jsonResponse["idToken"]; // This is your auth token
FirebaseManager.Instance.uid = (string)jsonResponse["localId"]; // this is your "uid"
});
// I have a list of users in my db keyed by the "uid" -- I access them like this
UnityHTTP.Request fullnameRequest = new UnityHTTP.Request ("get",
<YOUR-DATABASE-ROOT-URL-HERE>
+ "/users/" + FirebaseManager.Instance.uid + ".json?auth=" + FirebaseManager.Instance.idToken);
fullnameRequest.Send ((request) => {
Debug.Log(request.response.Text);
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error getting user info. Server returned null or malformed response");
}
FirebaseManager.Instance.fullname = (string)jsonResponse["fullname"];
FirebaseManager.Instance.groupId = (string)jsonResponse["group"]; // just storing this in memory
});
So I don't think there is any harm in using the URL, just make sure you budget time for more work when things change.

what API Gateway methods support Authorization?

When I create a resource/method in AWS API Gateway API I can create one of the following methods: DELETE, GET, HEAD, OPTIONS, PATCH or POST.
If I choose GET then API Gateway doesn't pass authentication details; but for POST it does.
For GET should I be adding the cognito credentials to the URL of my GET? or just never use GET and use POST for all authenticated calls?
My set-up in API Gateway/Lambda:
I created a Resource and two methods: GET and POST
Under Authorization Settings I set Authorization to AWS_AIM
For this example there is no Request Model
Under Method Execution I set Integration type to Lambda Function and I check Invoke with caller credentials (I also set Lambda Region and Lambda Function)
I leave Credentials cache unchecked.
For Body Mapping Templates, I set Content-Type to `application/json' and the Mapping Template to
{ "identity" : "$input.params('identity')"}
In my Python Lambda function:
def lambda_handler(event, context):
print context.identity
print context.identity.cognito_identity_id
return True
Running the Python function:
For the GET context.identity is None
For the POST context.identity has a value and context.identity.cognito_identity_id has the correct value.
As mentioned in comments: all HTTP methods support authentication. If the method is configured to require authentication, authentication results should be included in the context for you to access via mapping templates to pass down stream as contextual information.
If this is not working for you, please update your question to reflect:
How your API methods are configured.
What your mapping template is.
What results you see in testing.
UPDATE
The code in your lambda function is checking the context of the Lambda function, not the value from API Gateway. To access the value passed in from API Gateway, you would need to use event.identity not context.identity.
This would only half solve your problem as you are not using the correct value to access the identity in API gateway. That would be $context.identity.cognitoIdentityId (assuming you are using Amazon Cognito auth). Please see the mapping template reference for a full guide of supported variables.
Finally, you may want to consider using the template referenced in this question.