How to implemente oauth with Here in Flutter

1.5k views Asked by At

I'm getting this error when i trying to get oauth token with Here APi

Here is the error

wrong.","error":"invalid_client","error_description":"errorCode: '401300'. Signature mismatch. Authorization signature or client credential is wrong."}

This is my oauth code in Flutter

import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:let_log/let_log.dart';

class Oauthv1 {
  String oauthv1BaseUrl = "account.api.here.com";

  bool isJson = false;

  final String consumerKey, consumerKeySecret, accessToken, accessTokenSecret;

  Hmac _sigHasher;

  Oauthv1(this.consumerKey, this.consumerKeySecret, this.accessToken,
      this.accessTokenSecret) {
    var bytes = utf8.encode("$consumerKeySecret");
    _sigHasher = new Hmac(sha256, bytes);
  }

  Oauthv1 forceXml() {
    this.isJson = false;
    return this;
  }

  Future<http.Response> request(Map<String, String> data) {
    if (isJson) {
      data["format"] = "json";
    }
    return _callGetApi("oauth2/token", data);
  }

  Future<http.Response> _callGetApi(String url, Map<String, String> data) {
    Uri requestUrl = Uri.https(oauthv1BaseUrl, url);

    print(data["grant_type"]);
    _setAuthParams("POST", requestUrl.toString(), data);

    requestUrl = Uri.https(requestUrl.authority, requestUrl.path, data);

    String oAuthHeader = _generateOAuthHeader(data);

    Map<String, String> _headers = {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': '$oAuthHeader'
    };

    //Logger.debug(_headers);

    // Build the OAuth HTTP Header from the data.
    // Build the form data (exclude OAuth stuff that's already in the header).
//    var formData = _filterMap(data, (k) => !k.startsWith("oauth_"));

    //Logger.debug(requestUrl);
    //Logger.warn(data);
    Logger.debug(_headers);

    return _sendGetRequest(requestUrl, {'grant_type': 'client_credentials'}, _headers);
  }

  void _setAuthParams(
      String requestMethod, String url, Map<String, String> data) {
    // Timestamps are in seconds since 1/1/1970.
    // var timestamp = new DateTime.now().toUtc().difference(_epochUtc).inSeconds;
    /* var millisecondsSinceEpoch =
        new DateTime.now().toUtc().millisecondsSinceEpoch;
    var timestamp = (millisecondsSinceEpoch / 100).round(); */

    var ms = (new DateTime.now()).millisecondsSinceEpoch;
    var timestamp = (ms / 1000).round();

    // Add all the OAuth headers we'll need to use when constructing the hash.
    data["oauth_consumer_key"] = consumerKey;
    data["oauth_signature_method"] = "HMAC-SHA256";
    data["oauth_timestamp"] = timestamp.toString();
    data["oauth_nonce"] =
        _randomString(8); // Required, but Twitter doesn't appear to use it
    if (accessToken != null && accessToken.isNotEmpty)
      data["oauth_token"] = accessToken;
    data["oauth_version"] = "1.0";

    // Generate the OAuth signature and add it to our payload.
    data["oauth_signature"] =
        _generateSignature(requestMethod, Uri.parse(url), data);
  }

  /// Generate an OAuth signature from OAuth header values.
  String _generateSignature(
      String requestMethod, Uri url, Map<String, String> data) {
    var sigString = _toQueryString(data);
    var fullSigData =
        "$requestMethod&${_encode(url.toString())}&${_encode(sigString)}";

    return base64.encode(_hash(fullSigData));
  }

  /// Generate the raw OAuth HTML header from the values (including signature).
  String _generateOAuthHeader(Map<String, String> data) {
    var oauthHeaderValues = _filterMap(data, (k) => k.startsWith("oauth_"));

    return "OAuth " + _toOAuthHeader(oauthHeaderValues);
  }

  /// Send HTTP Request and return the response.
  Future<http.Response> _sendGetRequest(Uri fullUrl, Map<String, String> data,
      Map<String, String> headers) async {
    return await http.post("https://account.api.here.com/oauth2/token", body: 'grant_type=client_credentials', headers: headers);
  }

  Map<String, String> _filterMap(
      Map<String, String> map, bool test(String key)) {
    return new Map.fromIterable(map.keys.where(test), value: (k) => map[k]);
  }

  String _toQueryString(Map<String, String> data) {
    var items = data.keys.map((k) => "$k=${_encode(data[k])}").toList();
    items.sort();

    return items.join("&");
  }

  String _toOAuthHeader(Map<String, String> data) {
    var items = data.keys.map((k) => "$k=\"${_encode(data[k])}\"").toList();
    items.sort();

    return items.join(", ");
  }

  List<int> _hash(String data) => _sigHasher.convert(data.codeUnits).bytes;

  String _encode(String data) => percent.encode(data.codeUnits);

  String _randomString(int length) {
    var rand = new Random();
    var codeUnits = new List.generate(length, (index) {
      return rand.nextInt(26) + 97;
    });

    return new String.fromCharCodes(codeUnits);
  }
}

I send all headers and body request but response failed. The body request is

grant_type=client_credentials

I don't really know which data when not send or which param is missed. I'm using flutter Oauth 1.0. The auth working fine in postman but integration in flutter not work.

When i print _headers from sendgetrequest() method, it print this : {Content-Type: application/x-www-form-urlencoded, Authorization: OAuth oauth_consumer_key="o2zr*****bXuA", oauth_nonce="oaqpiovg", oauth_signature="H7M92BoEneotellYHqJCMkMfLOq9sMrm1R5KdtS8lAM%3D", oauth_signature_method="HMAC-SHA256", oauth_timestamp="1601165843", oauth_version="1.0"}

you can see the headers is correctly formated

1

There are 1 answers

1
lohnsonok On

I solved this by follow step by step: Create OAuth 1.0 signature section on here Documentation and it worked fine

The problem was that the order was not followed correctly in the oauth parameters.