将自定义 Oauth 提供程序与 firebase.auth().signInWithRedirect 集成?

2024-02-01

我使用以下命令设置了 Twitch OAuth 集成Instagram 示例 https://github.com/firebase/functions-samples/tree/master/instagram-auth,现在我可以通过打开登录到我的应用程序popup.html该示例给我的页面。

这是我改编的代码:

'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const { AuthorizationCode } = require('simple-oauth2');
const fetch = require('node-fetch');

// Firebase Setup
const admin = require('firebase-admin');
// @ts-ignore
const serviceAccount = require('./service-account.json');
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`,
});

const OAUTH_REDIRECT_URI = `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`;;
const OAUTH_SCOPES = 'user:read:email';

/**
 * Creates a configured simple-oauth2 client for Twitch.
 */
function twitchOAuth2Client() {
  // Twitch OAuth 2 setup
  // TODO: Configure the `twitch.client_id` and `twitch.client_secret` Google Cloud environment variables.
  const credentials = {
    client: {
      id: functions.config().twitch.client_id,
      secret: functions.config().twitch.client_secret,
    },
    auth: {
      tokenHost: 'https://id.twitch.tv',
      tokenPath: '/oauth2/token',
      authorizePath: '/oauth2/authorize',
    },
    options: {
      bodyFormat: 'json',
      authorizationMethod: 'body',
    },
  };
  return new AuthorizationCode(credentials);
}

/**
 * Redirects the User to the Twitch authentication consent screen. Also the 'state' cookie is set for later state
 * verification.
 */
exports.redirect = functions.https.onRequest((req, res) => {
  const authorizationCode = twitchOAuth2Client();

  cookieParser()(req, res, () => {
    const state = req.cookies.__session || crypto.randomBytes(20).toString('hex');
    console.log('Setting verification state:', state);
    res.cookie('__session', state.toString(), { maxAge: 3600000, httpOnly: true });
    const redirectUri = authorizationCode.authorizeURL({
      redirect_uri: OAUTH_REDIRECT_URI,
      scope: OAUTH_SCOPES,
      state: state,
    });
    console.log('Redirecting to:', redirectUri);
    res.redirect(redirectUri);
  });
});

/**
 * Exchanges a given Twitch auth code passed in the 'code' URL query parameter for a Firebase auth token.
 * The request also needs to specify a 'state' query parameter which will be checked against the 'state' cookie.
 * The Firebase custom auth token, display name, photo URL and Twitch acces token are sent back in a JSONP callback
 * function with function name defined by the 'callback' query parameter.
 */
exports.token = functions.https.onRequest((req, res) => {
  const authorizationCode = twitchOAuth2Client();

  try {
    cookieParser()(req, res, async () => {
      try {
        console.log('Received verification state:', req.cookies.__session);
        console.log('Received state:', req.query.state);
        if (!req.cookies.__session) {
          throw new Error(
            'State cookie not set or expired. Maybe you took too long to authorize. Please try again.'
          );
        } else if (req.cookies.__session !== req.query.state) {
          throw new Error('State validation failed');
        }
      } catch (error) {
        return res.jsonp({ error: error.toString() });
      }

      let accessToken;
      try {
        console.log('Received auth code:', req.query.code);
        const options = {
          client_id: functions.config().twitch.client_id,
          client_secret: functions.config().twitch.client_secret,
          code: req.query.code,
          grant_type: 'authorization_code',
          redirect_uri: OAUTH_REDIRECT_URI,
        };
        console.log('Asking token with options', JSON.stringify(options));
        accessToken = await authorizationCode.getToken(options);
        console.log('Auth code exchange result received');

        const twitchUser = await getTwitchUser(accessToken.toJSON().access_token);

        // Create a Firebase account and get the Custom Auth Token.
        const firebaseToken = await createFirebaseAccount(twitchUser);

        // Serve an HTML page that signs the user in and updates the user profile.
        return res.jsonp({ token: firebaseToken });
      } catch (error) {
        return res.jsonp({ error: error.toString() });
      }
    });
  } catch (error) {
    return res.jsonp({ error: error.toString() });
  }
});

/**
 * Creates a Firebase account with the given user profile and returns a custom auth token allowing
 * signing-in this account.
 *
 * @returns {Promise<string>} The Firebase custom auth token in a promise.
 */
async function createFirebaseAccount(twitchUser) {
  // The UID we'll assign to the user.
  const uid = `twitch:${twitchUser.id}`;

  // Save the access token to the Firebase Database.
  const db = admin.firestore();
  const databaseTask = db.collection('users').doc(uid).set(twitchUser);

  // Create or update the user account.
  const userCreationTask = admin
    .auth()
    .updateUser(uid, {
      displayName: twitchUser['display_name'],
      photoURL: twitchUser['profile_image_url'],
      email: twitchUser['email'],
    })
    .catch((error) => {
      // If user does not exists we create it.
      if (error.code === 'auth/user-not-found') {
        return admin.auth().createUser({
          uid: uid,
          displayName: twitchUser['display_name'],
          photoURL: twitchUser['profile_image_url'],
          email: twitchUser['email'],
        });
      }
      throw error;
    });

  // Wait for all async task to complete then generate and return a custom auth token.
  await Promise.all([userCreationTask, databaseTask]);
  // Create a Firebase custom auth token.
  const token = await admin.auth().createCustomToken(uid);
  console.log('Created Custom token for UID "', uid, '" Token:', token);
  return token;
}

async function getTwitchUser(accessToken) {
  console.log('Fetching Twitch user with access_token', accessToken);
  try {
    const response = await fetch('https://api.twitch.tv/helix/users', {
      method: 'GET',
      headers: {
        'Client-Id': functions.config().twitch.client_id,
        Authorization: 'Bearer ' + accessToken,
      },
    });
    const data = await response.json();
    return { ...data.data[0], access_token: accessToken };
  } catch (error) {
    console.error(error);
  }
}

不过,我想使用以下方式登录 Twitchfirebase.auth().signInWithRedirect()我已经用于 Facebook 和 Google 的方法,不幸的是我找不到任何关于此的文档,并且Facebook 提供商源代码 https://github.com/firebase/firebase-js-sdk/blob/a9ba7ba7c2b9da8bd16f14c0062593bd8d941124/packages-exp/auth-exp/src/core/providers/facebook.ts指的是一些externs.* https://github.com/firebase/firebase-js-sdk/blob/a9ba7ba7c2b9da8bd16f14c0062593bd8d941124/packages-exp/auth-exp/src/core/providers/facebook.ts#L66-L73资源,所以我不知道如何适应我自己的需要。

现在我有两个端点/云功能:_twitchRedirect and _twitchToken,我应该怎么做才能将它们集成signInWithRedirect?


我也同样好奇,所以今天花了一点时间尝试一下。

简而言之,当使用 Firebase Auth 时,我相信providerId需要是现有受支持的提供商之一。

如果您升级到使用 Google Cloud Identity Platform,我相信您将能够配置自定义提供商,然后使用此功能进行身份验证:

  • https://cloud.google.com/identity-platform https://cloud.google.com/identity-platform

我们可以看到firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect)与这里的许多提供者一起使用,例如。

  • https://cloud.google.com/identity-platform/docs/web/apple https://cloud.google.com/identity-platform/docs/web/apple
  • https://cloud.google.com/identity-platform/docs/web/microsoft https://cloud.google.com/identity-platform/docs/web/microsoft

除了我们通过标准 Firebase Auth 获得的这些提供商选择之外,Google Cloud Identity Platform 还允许我们添加 SAML 和 OpenID Connect (OIDC) 集成:

  • https://cloud.google.com/identity-platform/docs/web/saml https://cloud.google.com/identity-platform/docs/web/saml
  • https://cloud.google.com/identity-platform/docs/web/oidc https://cloud.google.com/identity-platform/docs/web/oidc

当使用其中任何一个添加新的身份提供商时,我们可以指定要使用的“提供商 ID”(前缀为saml. or oidc.)。然后将此自定义提供商 ID 与firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect) 如上所述。

例如,如果我创建了一个 ID 为的新身份提供者oidc.foo,我的集成代码最终将如下所示:

const provider = new firebase.auth.OAuthProvider('oidc.foo');

firebase.auth().signInWithPopup(provider)
  .then((result) => {
    // result.credential is a firebase.auth.OAuthCredential object.
    // result.credential.providerId is equal to 'oidc.foo'.
    // result.credential.idToken is the OIDC provider's ID token.
  })
  .catch((error) => {
    // Handle error.
  });

根据我对此的理解,我相信我们目前只能以这种方式添加自定义提供程序,前提是它们符合 OpenID Connect (OIDC) 标准(包括 OIDC Discovery 部分,该部分使用/.well-known/openid-configuration URL):

注意:如果您的 OIDC 提供商不遵守OIDC 发现规范 https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig,它不适用于 Identity Platform。

据我所知,目前实现“普通”OAuth2 提供程序的最佳方法是您上面使用的自定义后端函数流(基于 Firebase Auth 示例)。


作为解决这个问题的一部分,我决定看看如果我使用的提供商 ID 与我的帐户中配置的任何内容都不匹配会发生什么(这是一个相当详细的步骤,主要答案已经包含在上面,但是这可能有助于提供更多背景信息/帮助某人,因此将其包含在这里)

var provider = new firebase.auth.OAuthProvider("foo.example.com");
    firebase
      .auth()
      .signInWithRedirect(provider)
      .then((result) => console.log("OAuthProvider:", result))
      .catch((error) => console.log("OAuthProvider::error:", error));

firebase
  .auth()
  .getRedirectResult()
  .then((result) => console.log("RedirectResult:", result))
  .catch((error) => console.log("RedirectResult::error:", error));

首先我去这个auth/auth-domain-config-required error:

OAuthProvider::error: {
    "code": "auth/auth-domain-config-required",
    "message": "Be sure to include authDomain when calling firebase.initializeApp(), by following the instructions in the Firebase console."
}

我想也许这应该设置为我想要登录的 OAuth 提供程序,所以我设置authDomain在我的 firebase 配置中foo.myauthprovider.com,但是当我打电话时signInWithRedirect,它尝试加载以下 URL(其中apiKey是我的 firebase 项目的 API 密钥),无法加载:

https://foo.myauthprovider.com/__/auth/handler?apiKey=REDACTED&appName=%5BDEFAULT%5D&authType=signInViaRedirect&providerId=foo.example.com&redirectUrl=http%3A%2F%2Flocalhost%3A3000%2F&v=7.14.5

This /__/auth/handlerURL 是 Firebase Auth 保留 URL 的一部分,您可以在以下位置阅读更多信息:

  • https://firebase.google.com/docs/hosting/reserved-urls#auth_helpers https://firebase.google.com/docs/hosting/reserved-urls#auth_helpers

在 StackOverflow 答案中对此进行了更好的解释,但这基本上是 Firebase Auth 用来处理 OAuth 回调的方法,以避免需要在前端公开敏感凭据,因此用户不需要始终实现自己的处理程序):

  • 为什么 Firebase 身份验证在返回我的应用程序之前使用“中间件”重定向? https://stackoverflow.com/questions/53990604/why-does-firebase-auth-uses-a-middleware-redirect-before-returning-to-my-app/54002778#54002778

改变authDomain我的 firebase 项目的实际自定义域修复了该问题,然后导致以下结果auth/operation-not-allowed当我尝试重定向时出错:

RedirectResult::error: u {code: "auth/operation-not-allowed", message: "The identity provider configuration is not found.", a: null}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将自定义 Oauth 提供程序与 firebase.auth().signInWithRedirect 集成? 的相关文章

随机推荐

  • Spark:如何将 RDD[T]` 拆分为 Seq[RDD[T]] 并保留顺序

    如何有效拆分RDD T into a Seq RDD T Iterable RDD T with n元素并保留原始顺序 我希望能够写出这样的东西 RDD 1 2 3 4 5 6 7 8 9 split 3 这应该会导致类似的结果 Seq R
  • 重复使用范围进行迭代

    我试图了解如何将范围与迭代器一起使用 如果我声明一个范围并将其与迭代器一起使用 是否可以与另一个迭代器一起重复使用该范围 例如 这不会编译 fn main let smallr 0 10 for i in smallr println va
  • Dart/Flutter 按位移位运算符返回不同的值

    与移动 桌面相比 Dart Flutter 中的按位移位运算符在 Web 中返回不同的值 运行以下代码 var checksum 1150946793 var shift checksum lt lt 5 print shift Retur
  • 在 HTML 中向下滚动

    如何滚动myDiv在下面的代码中 此代码向下滚动整个浏览器窗口的内容 我只需要向下滚动一个分割层
  • 为什么“取消引用”和“地址”运算符位于左侧?

    在 C 以及其他一些类似 C 的语言 中 我们有 2 个一元运算符用于处理指针 解引用运算符 和 地址 运算符 他们是left一元运算符 这会带来运算顺序的不确定性 例如 ptr gt field or arr id 操作顺序is标准严格定
  • 计算数据帧中数字连续出现的次数

    我有一个包含 1 和 0 的虚拟列的数据框 我想计算每行 1 或 0 出现的次数 每次都从 0 开始 对 1 进行累加计数 对 0 进行倒数计数下面是一个例子 import pandas as pd df pd DataFrame Dumm
  • Excel:检查单元格是否包含文本字符串中的数字

    我有一个工作表 每个单元格中都有文本字符串 文章标题 我想知道单元格是否包含数字 例如 This is 3 gt TRUE Red balloon gt FALSE It s 10 things gt TRUE Update 这里的每个答案
  • python urllib2:连接被对等方重置

    我有一个 Perl 程序 可以从我的大学图书馆的数据库中检索数据 并且运行良好 现在想用python重写但是遇到问题
  • 无法单击 iframe 中的下拉菜单 - Selenium Python

    问题 尝试使用 Chrome 驱动程序和 Selenium 以及 Python 单击 iframe 内的下拉菜单 大家好 昨天一位用户好心地帮助我解决了一个新手查询 我无法单击 url 中的链接 这是因为我必须切换到 iframe 这部分代
  • 如何在C#中手动使用资源(无IDE行为)?

    我正在使用 Visual Studio 学习 C 我想更深入一点 我想知道如何手动使用项目资源 不是通过 IDE 我搜索了该网站 但我刚刚看到这个家伙的帖子如何在 NET中创建和使用资源 https stackoverflow com qu
  • 在 Firefox 扩展中使用 indexedDB

    我无法在 Firefox 扩展中使用 indexedDB 保存数据 我也找不到有关 indexedDB 和 Firefox 扩展的任何信息 有人处理过这个吗 唯一的问题是 对于indexedDB您需要一个窗口 除此之外 从附加组件使用它时没
  • XMLReader 未知

    按照此介绍 http csharp net tutorials com xml reading xml with the xmlreader class 可以通过导入命名空间来使用 XMLReader 类System Xml 在我的 Vis
  • 使用 SWAPI(星球大战 API)的 React 项目出现混合内容错误

    我构建一个 React 项目只是为了尝试一些东西 我在使用时遇到问题SWAPI 星球大战 API https swapi dev 我不断收到Mixed Content Error当我尝试在我的 React 项目中通过 axios 使用他们的
  • istio egressgateway:通过静态 IP 进行路由

    我尝试通过静态 IP 从 GKE 集群中的应用程序路由出站流量 因为目标服务器需要将 IP 列入白名单才能访问 我已经能够使用 terraformed nat 网关来执行此操作 但这会影响来自集群的所有流量 按照网站上的 istio 指南
  • 链表部分

    我想我可能做对了 headByRating 和 headByName 都引用相同的地址 我整天都在画图表尝试新事物等 但我并没有真正取得任何进展 我有两个列表指针 headByRating 和 headByName 以及两个节点指针 nex
  • 如何通过命令行在副本集中的辅助mongodb服务器中设置rs.slaveOk()?

    如何通过命令行在副本集中的辅助mongodb服务器中设置rs slaveOk 我尝试了以下方法 MONGO HOME bin mongo port MONGO PORT2 host MONGO SECONDARY2 eval printjs
  • 在 WebForm_OnSubmit 之后调用自定义客户端验证函数? 。网

    我想在我的 NET 验证器控件运行其 javascript 后运行一个简单的 JS 函数 我的onsubmit值是javascript return WebForm OnSubmit 它似乎是自动生成的 我无法更改它 我是否正确地假设我只需
  • SQL Server 中的 ALTER 与 DROP & CREATE

    SQL Server中创建存储过程后为什么要替换Create with Alter 如果我们不改变它 执行时会发生什么 除了检查是否存在并删除之外 还有更好的替代方法吗 Create如果表存在将会失败 Alter如果表不存在将会失败 如果你
  • Laravel dusk:使用 Dropzone.js 测试文件上传

    我正在使用拉拉维尔5 6 and Dusk对于这个具体的测试 我正在尝试在我的拖放区中断言文件上传 但我的 Dropzone 是以我没有的方式创建的file输入元素 所以我不能使用attach method 所以我尝试了以下方法 file
  • 将自定义 Oauth 提供程序与 firebase.auth().signInWithRedirect 集成?

    我使用以下命令设置了 Twitch OAuth 集成Instagram 示例 https github com firebase functions samples tree master instagram auth 现在我可以通过打开登