Firestore 云功能 apollo graphql 身份验证

Posted

技术标签:

【中文标题】Firestore 云功能 apollo graphql 身份验证【英文标题】:Firestore cloud functions apollo graphql authentication 【发布时间】:2020-08-17 13:33:34 【问题描述】:

我需要帮助让我的 Firebase Apollo/GraphQL Cloud Function 进行身份验证和接收查询请求。

我实现了一个 Apollo/GraphQL 服务器作为云函数 Firebase/Firestore 使用来自this post 的this repository。 我将云功能的权限设置为 allAuthenticatedUsers 我正在使用 Firebase Phone Authentication 进行身份验证。 我使用来自this *** answer 的代码来帮助构建 身份验证部分未包含在初始存储库中。

当权限设置为 allUsers 时,Apollo/GraphQL 函数可以正常工作(在 Playground 中测试)。在将权限设置为 allAuthenticatedUsers 并尝试发送经过身份验证的查询后,我收到以下错误响应:

Bearer error="invalid_token" error_description="The access token could not be verified"

我相信我在客户端发送的请求,或者对 ApolloServer 的验证和“上下文”的处理方面犯了错误。我已确认初始用户令牌是正确的。我目前的理论是我发送了错误的标头,或者在客户端或服务器级别以某种方式弄乱了语法。

解释我认为适当的请求流程应该是:

    在客户端生成令牌 从客户端发送的以令牌为标头的查询 ApolloServer 云函数接收请求 令牌已通过 Firebase 验证,提供新的已验证标头令牌 服务器接受带有新验证标头令牌的查询并返回数据

如果有人能解释如何将经过身份验证的有效客户端查询发送到 Firebase Apollo/GraphQL 云函数,我们将不胜感激。下面的服务器和客户端代码。

Server.js (ApolloServer)

/* Assume proper imports */
/* Initialize Firebase Admin SDK */
admin.initializeApp(
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "[db-url]",
);

/* Async verification with user token */
const verify = async (idToken) => 
  var newToken = idToken.replace("Bearer ", "");
  let header = await admin.auth().verifyIdToken(newToken)
    .then(function(decodedToken) 
      let uid = decodedToken.uid;
      // Not sure if I should be using the .uid from above as the token?
      // Also, not sure if returning the below object is acceptable, or
      // if this is even the correct header to send to firebase from Apollo
      return 
        "Authorization": `Bearer $decodedToken`
      
    ).catch(function(error) 
      // Handle error
      return null
    );
  return header


/* Server */
function gqlServer() 
  const app = express();

  const apolloServer = new ApolloServer(
    typeDefs: schema,
    resolvers,
    context: async ( req, res ) => 
      const verified = await verify(req.headers.Authorization)
      console.log('log verified', verified)
      return 
        headers: verified ? verified: '',
        req, 
        res,
      
    ,
    // Enable graphiql gui
    introspection: true,
    playground: true
  );
  
  apolloServer.applyMiddleware(app, path: '/', cors: true);

  return app;


export default gqlServer;

Client.js (ApolloClient)

使用these instructions构造的客户端查询。

/* Assume appropriate imports */
/* React Native firebase auth */
firebase.auth().onAuthStateChanged(async (user) => 
    const userToken = await user.getIdToken();
    
    /* Client creation */
    const client = new ApolloClient(
      uri: '[Firebase Cloud Function URL]',
      headers: 
        Authorization: userToken ? `Bearer $userToken` : ''
      ,
      cache: new InMemoryCache(),
    );
    /* Query test */
    client.query(
      query: gql`
        
          hello
        
      `
    ).then(
      (result) => console.log('log query result', result)
    ).catch(
      (error) => console.log('query error', error)
    )
)

更新 05/03/20

我可能已经找到了错误的根源。在我确认之前我不会发布答案,但这是更新。看起来allAuthenticatedUsers 是特定于 Google 帐户而不是 Firebase 的角色。请参阅this part of the google docs 和this *** answer。

我会做一些测试,但解决方案可能是将权限更改为allUsers,这可能仍需要身份验证。如果我可以让它工作,我会更新一个答案。

【问题讨论】:

" 授权:Bearer $userToken ? userToken : '' " 这行正确吗? @gstvg,根据关于客户端的说明中的第 2 步,这是应该发送的标头Authorization: Bearer ID_TOKEN。所以我认为是的。参考文档 -cloud.google.com/functions/docs/securing/…, 是的,你的文档是正确的,我的评论的格式也很难识别,但我认为这一行有一个小错字:你正在做一个 ternay 操作,它总是返回userToken 单独,没有“Bearer”部分,不是吗? @gstvg 很好,我确实不小心把它们翻了。不幸的是,在修复操作并再次尝试查询后,结果仍然是相同的错误。我已经更新了上述问题以反映正确的操作。 解决方案有更新吗? 【参考方案1】:

我能够让事情正常进行。工作请求需要进行以下更改:

    更改云函数“调用者”角色以包含 allUsers 而不是 allAuthenticatedUsers。这是因为allUsers 角色使该功能可用于 http 请求(您仍然可以要求通过 sdk 验证进行身份验证) 调整服务器和客户端的代码,如下所示。对标头字符串构造进行了细微更改。

Server.js (ApolloServer)

/* Assume proper imports */
/* Initialize Firebase Admin SDK */
admin.initializeApp(
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "[db-url]",
);

/* Async verification with user token */
const verify = async (idToken) => 
  if (idToken) 
    var newToken = idToken.replace("Bearer ", "");
    // var newToken = idToken
    let header = await admin.auth().verifyIdToken(newToken)
      .then(function(decodedToken) 
        // ...
        return 
          "Authorization": 'Bearer ' + decodedToken
        
      ).catch(function(error) 
        // Handle error
        return null
      );
    return header
   else 
    throw 'No Access' 
  


/* Server */
function gqlServer() 
  const app = express();

  const apolloServer = new ApolloServer(
    typeDefs: schema,
    resolvers,
    context: async ( req, res ) => 
      // headers: req.headers,
      const verified = await verify(req.headers.authorization)
      console.log('log verified', verified)
      return 
        headers: verified ? verified: '',
        req, 
        res,
      
    ,
    // Enable graphiql gui
    introspection: true,
    playground: true
  );
  
  apolloServer.applyMiddleware(app, path: '/', cors: true);

  return app;


export default gqlServer;

Client.js (ApolloClient)

/* Assume appropriate imports */
/* React Native firebase auth */
firebase.auth().onAuthStateChanged(async (user) => 
    const userToken = await user.getIdToken();
    
    /* Client creation */
    const userToken = await user.getIdToken();
    const client = new ApolloClient(
      uri: '[Firebase Cloud Function URL]',
      headers: 
        "Authorization": userToken ? 'Bearer ' + userToken : ''
      , 
      cache: new InMemoryCache(),
    );
    client.query(
      query: gql`
        
          hello
        
      `
    ).then(
      (result) => console.log('log query result', result)
    ).catch(
      (error) => console.log('query error', error)
    )
)

【讨论】:

以上是关于Firestore 云功能 apollo graphql 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Firestore - 云功能 - 获取 uid

Firebase 云功能 / Firestore

云功能有时会在触发另一个云功能后写入 FireStore

Firestore + 云功能:如何从另一个文档中读取

如何使用云功能从 Firestore 中删除数据

如何使用云功能删除 Firestore 中的文档