简体   繁体   English

Flutter with Firebase JWT 将 GraphQL (graphql_flutter) 请求发送到具有“格式错误的授权标头”的 Heroku Hasura

[英]Flutter with Firebase JWT sends GraphQL (graphql_flutter) request to Heroku Hasura that has a “Malformed Authorization header”

I've searched for answers but I haven't seen talk about this in the context of using Flutter, Firebase, and Hasura GraphQL together.我已经搜索了答案,但我没有看到在一起使用 Flutter、Firebase 和 Hasura GraphQL 的上下文中讨论过这个问题。

Working in Flutter, utilizing a Firebase authentication backend, I am acquiring a user JWT and passing it to a Heroku Hasura GraphQL endpoint (with a query).在 Flutter 中工作,利用 Firebase 身份验证后端,我正在获取用户 JWT 并将其传递给 Heroku Hasura GraphQL 端点(带有查询)。

Much of the setup and code follows and is inspired by a tutorial athttps://hasura.io/blog/build-flutter-app-hasura-firebase-part1/ , part 2 and part 3, and flutter graphql documentation at https://github.com/snowballdigital/flutter-graphql .大部分设置和代码都遵循并受到https://hasura.io/blog/build-flutter-app-hasura-firebase-part1/ 上的教程的启发,第 2 部分和第 3 部分,以及https 上的flutter graphql 文档: //github.com/snowballdigital/flutter-graphql

Firebase is successfully adding new user records to my GraphQL database. Firebase 已成功将新用户记录添加到我的 GraphQL 数据库。 Firebase is returning a JWT and it is getting added to the GraphQL AuthLink during the construction of the GraphQL client. Firebase 正在返回一个 JWT,它在构建 GraphQL 客户端期间被添加到 GraphQL AuthLink。 This is what the JWT looks like (obscuring personal information):这就是 JWT 的样子(隐藏个人信息):

HEADER:ALGORITHM & TOKEN TYPE标头:算法和令牌类型

{
    "alg": "RS256",
    "kid": "12809dd239d24bd379c0ad191f8b0edcdb9d3914",
    "typ": "JWT"
}

FULL PAYLOAD:DATA

{
    "iss": "https://securetoken.google.com/<firebase-app-id>",
    "aud": "<firebase-app-id>",
    "auth_time": 1598563214,
    "user_id": "iMovnQvpwuO8HiGOV82cYTmZRM92",
    "sub": "iMovnQvpwuO8HiGOV82cYTmZRM92",
    "iat": 1598635486,
    "exp": 1598639086,
    "email": "<user-email>",
    "email_verified": false,
    "firebase": {
        "identities": {
            "email": [
                "<user-email>"
            ]
        },
        "sign_in_provider": "password"
    }
}

The Decode JWT tool in the HASURA UI shows this error: HASURA UI 中的解码 JWT 工具显示此错误:

"claims key: 'https://hasura.io/jwt/claims' not found" “声明键:未找到‘https://hasura.io/jwt/claims’”

According to Hasura's documentation something like this should exist in the token:根据Hasura 的文档,令牌中应该存在这样的内容:

"https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles": ["editor","user", "mod"],
    "x-hasura-default-role": "user",
    "x-hasura-user-id": "1234567890",
    "x-hasura-org-id": "123",
    "x-hasura-custom": "custom-value"
  }

In the various tutorials and documentation I'm using, the only place I've seen a need for defining hasura custom claims is in the Firebase cloud function where a user is registered:在我使用的各种教程和文档中,我看到需要定义 hasura 自定义声明的唯一地方是在用户注册的 Firebase 云函数中:

exports.registerUser = functions.https.onCall(async (data, context) => {

const email = data.email;
const password = data.password;
const displayName = data.displayName;

if (email === null || password === null || displayName === null) {
    throw new functions.https.HttpsError('unauthenticated', 'missing information');
}

try {
    const userRecord = await admin.auth().createUser({
        email: email,
        password: password,
        displayName: displayName
    });

    const customClaims = {
        "https://hasura.io/jwt/claims": {
            "x-hasura-default-role": "user",
            "x-hasura-allowed-roles": ["user"],
            "x-hasura-user-id": userRecord.uid
        }
    };

    await admin.auth().setCustomUserClaims(userRecord.uid, customClaims);
    return userRecord.toJSON();

} catch (e) {
    throw new functions.https.HttpsError('unauthenticated', JSON.stringify(error, undefined, 2));
}

}); });

I'm too new to Firebase and to JWT to understand why the custom claims are not in the token.我对 Firebase 和 JWT 太陌生,无法理解为什么自定义声明不在令牌中。 I assumed Firebase would hand me a JWT with the custom claims embedded and that passing it to the Hasura backend would be enough.我认为 Firebase 会给我一个嵌入了自定义声明的 JWT,并将其传递给 Hasura 后端就足够了。

My Heroku Hasura application logs also shows this error:我的 Heroku Hasura 应用程序日志也显示此错误:

"Malformed Authorization header","code":"invalid-headers" "Malformed Authorization header","code":"invalid-headers"

Does Firebase needs further configuration in order to hand back the proper claims? Firebase 是否需要进一步配置才能交回正确的声明? Is the missing information in the JWT the same as the server-side error logged as "Malformed Authorization header" or do I need to set additional headers (see Flutter code below). JWT 中缺少的信息是否与记录为“格式错误的授权标头”的服务器端错误相同,或者我是否需要设置其他标头(请参阅下面的 Flutter 代码)。

Here is the GraphQL configuration code in Flutter:以下是 Flutter 中的 GraphQL 配置代码:

import 'dart:async';

import 'package:dailyvibe/services/jwt_service.dart';
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';

class AuthLink extends Link {
  AuthLink()
      : super(
          request: (Operation operation, [NextLink forward]) {
            StreamController<FetchResult> controller;

            Future<void> onListen() async {
              try {
                final String token = JWTSingleton.token;
                operation.setContext(<String, Map<String, String>>{
                  'headers': <String, String>{
                    'Authorization': '''bearer $token'''
                  }
                });
              } catch (error) {
                controller.addError(error);
              }

              await controller.addStream(forward(operation));
              await controller.close();
            }

            controller = StreamController<FetchResult>(onListen: onListen);

            return controller.stream;
          },
        );
}

class ConfigGraphQLClient extends StatefulWidget {
  const ConfigGraphQLClient({
    Key key,
    @required this.child,
  }) : super(key: key);
  final Widget child;

  @override
  _ConfigGraphQLClientState createState() => _ConfigGraphQLClientState();
}

class _ConfigGraphQLClientState extends State<ConfigGraphQLClient> {
  @override
  Widget build(BuildContext context) {
    final cache = InMemoryCache();

    final authLink = AuthLink()
        .concat(HttpLink(uri: 'https://<myapp>.herokuapp.com/v1/graphql'));

    final ValueNotifier<GraphQLClient> client = ValueNotifier(
      GraphQLClient(
        cache: cache,
        link: authLink,
      ),
    );

    return GraphQLProvider(
      client: client,
      child: CacheProvider(
        child: widget.child,
      ),
    );
  }
}

Answering my own question: custom claims were not in my JWT because I was not calling my registerUser cloud function from Flutter.回答我自己的问题:自定义声明不在我的 JWT 中,因为我没有从 Flutter 调用我的 registerUser 云函数。 Now I am, like this:现在我是这样的:

  final HttpsCallable callable = CloudFunctions.instance
      .getHttpsCallable(functionName: 'registerUser')
        ..timeout = const Duration(seconds: 30);
  await callable.call(<String, dynamic>{
    'email': email,
    'password': password,
  });

(This code snippet is thanks tohttps://hasura.io/blog/build-flutter-app-hasura-firebase-part3/ ) (此代码片段感谢https://hasura.io/blog/build-flutter-app-hasura-firebase-part3/

Instead I was using Firebase's createUserWithEmailAndPassword(email: email, password: password) method, which does not create custom claims or trigger the cloud function.相反,我使用了 Firebase 的createUserWithEmailAndPassword(email: email, password: password)方法,该方法不会创建自定义声明或触发云功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM