jonsaw / amazon-cognito-identity-dart

Unofficial Amazon Cognito Identity Provider Dart SDK, to easily add user sign-up and sign-in to your mobile and web apps with AWS.
MIT License
204 stars 93 forks source link

awsSig4 serviceName value/Unable to parse JWT token. #25

Closed idkjs closed 5 years ago

idkjs commented 5 years ago

@jonsaw great demo. Thanks for sharing.

I'm trying to call APPSYNC using this package in the example project. I'm guided by the example Appsync use case in @ncb000gt pull request.

Below is the UserService method in the UserService class modified. It throws the following error when I run it.

I/flutter (26309): {
I/flutter (26309):   "errors" : [ {
I/flutter (26309):     "errorType" : "UnauthorizedException",
I/flutter (26309):     "message" : "Unable to parse JWT token."
I/flutter (26309):   } ]
I/flutter (26309): }
  Future<UserService> _getValues(BuildContext context) async {
    try {
      await _userService.init();
      _isAuthenticated = await _userService.checkAuthenticated();
      if (_isAuthenticated) {
        // get user attributes from cognito
        _user = await _userService.getCurrentUser();

        // get session credentials
        final credentials = await _userService.getCredentials();
        _awsSigV4Client = new AwsSigV4Client(
            credentials.accessKeyId, credentials.secretAccessKey, _endpoint,
            region: _region,
            sessionToken: credentials.sessionToken,
            serviceName: 'AMAZON_COGNITO_USER_POOLS');

        // get previous count
        _citiesService = new CitiesService(_awsSigV4Client);
        _cities = await _citiesService.getCities();
        // _counterService = new CounterService(_awsSigV4Client);
        // _counter = await _counterService.getCounter();
      }
      return _userService;
    } on CognitoClientException catch (e) {
      if (e.code == 'NotAuthorizedException') {
        await _userService.signOut();
        Navigator.pop(context);
      }
      throw e;
    }
  }

In javascript aws-sdk the params we have to pass are: https://github.com/dabit3/appsync-react-native-with-user-authorization/blob/b312924fe57e0936186b4688d7c856b13e76c27b/App.js#L30-L37

const client = new Client({
  url: AppSync.graphqlEndpoint,
  region: AppSync.region,
  auth: {
    type: 'AMAZON_COGNITO_USER_POOLS',
    jwtToken: async () => (await Auth.currentSession()).getIdToken().getJwtToken(),
  }
});

I'm thinking that solution is to pass AMAZON_COGNITO_USER_POOLS to awsSigV4Client some way. Any ideas on how to do this correctly?

Thank you.

roger1345 commented 5 years ago

Hi, Same issue here. Any updates?

When I use a http GET and pass the Authentication header, the Api respond normally. But when I use the AwsSigV4Client, the "Unable to parse JWT token." error appears.

Best Regards

ncb000gt commented 5 years ago

@idkjs @roger1345

So, I'm not using lambda or any other aws services at the moment, but I am using cognito and verifying the jwt token in my own backend server (at the moment).

I'm getting the jwt token without producing my own. I'm not sure if this will work for you all, but I'm sharing in case it does:

    _user.getSession().getAccessToken().getJwtToken()

This provides me with the JWT token I need on the backend because I supply that through the authorization header like so:

    headers['Authorization'] = 'Bearer ' + token;

As I understand it, if you're using a lambda function through apigateway you can do the same thing using an authorization header and the lambda will process that as a jwt token. Again, I mention gateway because I suspect that is what appsync is using, but maybe not. Either way, maybe it'll help you figure something else out?

Hope it helps.

jonsaw commented 5 years ago

Depending on your Authorization Type

Screenshot 2019-03-12 at 4 00 16 PM

AWS Identity and Access Management (IAM)

You will need to sign the request:

import 'package:http/http.dart' as http;
import 'package:amazon_cognito_identity_dart/cognito.dart';
import 'package:amazon_cognito_identity_dart/sig_v4.dart';

void main() async {
  const _awsUserPoolId = 'ap-southeast-1_xxxxxxxxx';
  const _awsClientId = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';

  const _identityPoolId = 'ap-southeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
  final _userPool = CognitoUserPool(_awsUserPoolId, _awsClientId);

  final _cognitoUser = CognitoUser('+60100000000', _userPool);
  final authDetails =
      AuthenticationDetails(username: '+60100000000', password: 'p@ssW0rd');

  CognitoUserSession _session;
  try {
    _session = await _cognitoUser.authenticateUser(authDetails);
  } catch (e) {
    print(e);
    return;
  }

  final _credentials = CognitoCredentials(_identityPoolId, _userPool);
  await _credentials.getAwsCredentials(_session.getIdToken().getJwtToken());

  final _endpoint =
      'https://xxxxxxxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-southeast-1.amazonaws.com';
  final sigV4Client = AwsSigV4Client(
    _credentials.accessKeyId,
    _credentials.secretAccessKey,
    _endpoint,
    serviceName: 'appsync',
    sessionToken: _credentials.sessionToken,
    region: 'ap-southeast-1',
  );
  final query = '''mutation CreateItem {
      createItem(name: "Some Name") {
        name
      }
    }''';

  final _signedRequest =
      SigV4Request(sigV4Client, method: 'POST', path: '/graphql', headers: {
    'Content-Type': 'application/graphql; charset=utf-8',
  }, body: {
    'operationName': 'CreateItem',
    'query': query,
  });
  print(_signedRequest.headers);
  http.Response response;
  try {
    response = await http.post(_signedRequest.url,
        headers: _signedRequest.headers, body: _signedRequest.body);
  } catch (e) {
    print(e);
  }
  print(response.body);
}

Amazon Cognito User Pool

A simple Authorization with JWT token will do.

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:amazon_cognito_identity_dart/cognito.dart';

void main() async {
  const _awsUserPoolId = 'ap-southeast-1_xxxxxxxxx';
  const _awsClientId = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';

  const _identityPoolId = 'ap-southeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
  final _userPool = CognitoUserPool(_awsUserPoolId, _awsClientId);

  final _cognitoUser = CognitoUser('+60100000000', _userPool);
  final authDetails =
      AuthenticationDetails(username: '+60100000000', password: 'p@ssW0rd');

  CognitoUserSession _session;
  try {
    _session = await _cognitoUser.authenticateUser(authDetails);
  } catch (e) {
    print(e);
    return;
  }

  final _credentials = CognitoCredentials(_identityPoolId, _userPool);
  await _credentials.getAwsCredentials(_session.getIdToken().getJwtToken());

  final _endpoint =
      'https://xxxxxxxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-southeast-1.amazonaws.com';

  final body = {
    'operationName': 'CreateProfile',
    'query': '''mutation CreateItem {
        createItem(name: "Some Name") {
          name
        }
      }''',
  };
  http.Response response;
  try {
    response = await http.post(
      '$_endpoint/graphql',
      headers: {
        'Authorization': _session.getAccessToken().getJwtToken(),
        'Content-Type': 'application/json',
      },
      body: json.encode(body),
    );
  } catch (e) {
    print(e);
  }
  print(response.body);
}
arunpkumar92 commented 4 years ago

Hi @jonsaw ,

This is great and it is worked for me.

I have doubt I have a flutter application and I need login only some areas. Remain area how I fetch data from appSync using unauthenticated.

and we hardcoding user name and password with in the app is not good, right?

Please help me on this.