微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决将自定义 Oauth 提供程序与 firebase.auth().signInWithRedirect?

我使用 Instagram example 设置了 Twitch OAuth 集成,现在我可以通过打开示例提供的 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,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'],});
      }
      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);
  }
}

不过,我想使用已用于 Facebook 和 Google 的 firebase.auth().signInWithRedirect() 方法登录 Twitch,不幸的是我找不到任何关于此的文档,Facebook provider source code 指的是一些 externs.* 资源,所以我不确定如何根据我自己的需要调整它。

现在我有两个端点/云函数_twitchRedirect_twitchToken,我应该怎么做才能将它们与 signInWithRedirect 集成?

解决方法

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

简而言之,在使用 Firebase 身份验证时,我认为 providerId 需要是现有支持的提供程序之一。

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

我们可以看到 firebase.auth.OAuthProviderfirebase.auth().signInWithPopup(或 firebase.auth().signInWithRedirect)与此处的许多提供程序一起使用,例如。

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

在使用其中任何一个添加新身份提供者时,我们可以指定要使用的“提供者 ID”(以 saml.oidc. 为前缀)。然后,如上所述,此自定义提供程序 ID 与 firebase.auth.OAuthProviderfirebase.auth().signInWithPopup(或 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) 标准(包括使用 /.well-known/openid-configuration网址):

注意:如果您的 OIDC 提供商不符合 OIDC specification for discovery,它将无法与 Identity Platform 配合使用。

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


作为解决这个问题的一部分,我决定看看如果我使用的提供商 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 错误:

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 提供程序,所以我在 Firebase 配置中将 authDomain 设置为 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

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

并且在这个 StackOverflow answer 中解释得更好一点,但基本上是 Firebase Auth 用来处理 OAuth 回调以避免需要在前端公开敏感凭据的内容,因此用户不需要实现自己的所有处理程序时间):

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

RedirectResult::error: u {code: "auth/operation-not-allowed",message: "The identity provider configuration is not found.",a: null}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。