如何在 Firestore Cloud Functions 上使用 Apollo Server GraphQL 在 TypeScript 中处理身份验证

Posted

技术标签:

【中文标题】如何在 Firestore Cloud Functions 上使用 Apollo Server GraphQL 在 TypeScript 中处理身份验证【英文标题】:How to handle authentication in TypeScript with Apollo Server GraphQL on Firestore Cloud Functions 【发布时间】:2021-05-08 03:57:39 【问题描述】:

我一直在关注这篇文章,但尝试使用 TypeScript 而不是 javascript: https://medium.com/@piuccio/running-apollo-server-on-firebase-cloud-functions-265849e9f5b8

但是,当未正确提供 Authorization 标头时,我无法获得正确的行为。在下面的代码中,我抛出了一个Error("No token")。我还尝试返回一个被拒绝的Promise,但这给了我一个带有 的HTTP 500 错误(一个空的JSON 响应)。

import gql from 'graphql-tag';
import  makeExecutableSchema  from 'graphql-tools';
import  Config  from 'apollo-server-cloud-functions';
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';


... resolvers, etc go here...


async function getUser(token: string): Promise<admin.auth.DecodedIdToken> 
    functions.logger.info('token', token);
    if (token === undefined || token === '') 
        throw new Error("No token");
    
    const idToken = token.split('Bearer ')[1];
    const decodedIdToken = await admin.auth().verifyIdToken(idToken);
    return decodedIdToken;


export const config: Config = 
    schema: makeExecutableSchema(
        typeDefs,
        resolvers,
    ),

    // TODO: DISABLE THESE IN PRODUCTION!
    introspection: true,
    playground: true,

    context: (req, context): Promise<admin.auth.DecodedIdToken> => 
        // See https://www.apollographql.com/docs/apollo-server/security/authentication/
        functions.logger.info('req headers', req.headers);
        const token = req.headers.authorization || '';
        const user = getUser(token);
        // if (!user) throw new AuthenticationError('you must be logged in');
        return user;
    ,
    formatError: formatError,
;

对于 API 支持匿名访问的情况,如何从 getUser() 返回有效的 Promise

我更喜欢使用 Apollo 服务器 AuthenticationError(作为我的身份验证检查),但我无法从 getUser() 返回 null

【问题讨论】:

【参考方案1】:

按照这里的建议:How to reject in async/await syntax?

我可以将Promise 的类型修改为DecodedIdToken | null 并将null 作为有效值返回。如果我 reject Promise 则抛出异常并带有错误字符串。

async function getUser(token: string): Promise<DecodedIdToken | null> 
    functions.logger.info('token', token);
    if (token === undefined || token === '') 
        return null;
        // return Promise.reject("No token");
    
    const idToken = token.split('Bearer ')[1];
    const decodedIdToken = admin.auth().verifyIdToken(idToken);
    return decodedIdToken;


export const config: Config = 
    schema: makeExecutableSchema(
        typeDefs,
        resolvers,
    ),

    // TODO: DISABLE THESE IN PRODUCTION!
    introspection: true,
    playground: true,

    context: async (req, context): Promise<DecodedIdToken | null> => 
        // See https://www.apollographql.com/docs/apollo-server/security/authentication/
        functions.logger.info('req headers', req.headers);
        const token = req.headers.authorization || '';
        try 
            const user = await getUser(token);
            return Promise.resolve(user);

         catch (err) 
            return Promise.resolve(null); // "No token"
            // throw new AuthenticationError('you must be logged in');
        
    ,
    formatError: formatError,
;

因此,根据您是否希望抛出异常,或者您是否想使用自己的异常(例如 FireStore 的 AuthenticationError),您可以按照自己的方式构建它。

以下是不同的选项:

Promise.resolve(value)
Promise.resolve(null)
Promise.reject("Not valid")  -> throws exception Error("Not valid")
throw new AuthenticationError("Not authorized")

...或使用构造函数语法:

return new Promise<DecodedIdToken | null>((resolve, reject) => 
  if (valid) 
    resolve(value);
   else 
    reject("Not valid");
  
);

【讨论】:

以上是关于如何在 Firestore Cloud Functions 上使用 Apollo Server GraphQL 在 TypeScript 中处理身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Cloud 函数中读取 Firestore 文档更新之前/之后的值?

如何在 Cloud Firestore 中使用文档 ID 执行集合组查询

如何修复颤动的 cloud_firestore 依赖项?

Cloud Functions:如何将 Firestore 集合复制到新文档?

Cloud Functions、Cloud Firestore、Cloud Storage:如何防范机器人?

如何使用flutter cloud_firestore包传递firestore auth token