firebase - 将自定义 Oauth 提供程序与 firebase.auth().signInWithRedirect 集成?
问题描述
我使用Instagram 示例设置了 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, 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);
}
}
不过,我想使用firebase.auth().signInWithRedirect()
我已经用于 Facebook 和 Google 的方法登录 Twitch,不幸的是我找不到任何关于此的文档,而且Facebook 提供程序源代码引用了一些externs.*
资源,所以我不确定如何适应我自己的需要。
现在我有两个端点/云功能:_twitchRedirect
并且_twitchToken
,我应该怎么做才能将它们与它们集成signInWithRedirect
?
解决方案
我同样好奇,所以今天花了一点时间玩弄东西。
简而言之,在使用 Firebase 身份验证时,我相信providerId
需要成为现有支持的提供商之一。
如果您升级到使用 Google Cloud Identity Platform,我相信您将能够配置自定义提供程序,然后使用此功能进行身份验证:
我们可以看到firebase.auth.OAuthProvider
和firebase.auth().signInWithPopup
(或firebase.auth().signInWithRedirect
)在这里与许多提供者一起使用,例如。
- https://cloud.google.com/identity-platform/docs/web/apple
- https://cloud.google.com/identity-platform/docs/web/microsoft
除了我们通过标准 Firebase 身份验证获得的这些提供商选择之外,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/oidc
当使用其中任何一个添加新的身份提供者时,我们可以指定要使用的“提供者 ID”(以saml.
或为前缀oidc.
)。然后,此自定义提供程序 ID 与firebase.auth.OAuthProvider
和firebase.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
URL 的 OIDC 发现部分):
注意:如果您的 OIDC 提供者不符合OIDC 发现规范,则它将无法与 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
错误:
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 项目),未能加载:
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 答案中解释得更好,但基本上是 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}
推荐阅读
- jquery - 隐藏帐户选项 - 跨度值
- javascript - 数组映射的函数的异步/等待返回值
- amazon-web-services - 无法在 Cloudfront 层上的 Lambda + API Gateway + Cloudfront 上启用 CORS
- php - 如何解决将数据插入数据库的错误
- angular - 异步初始化 MatPaginator
- c++ - 我不知道如何在 ms Visual Studio C++ 中解决此运行时检查失败 #2-S 错误
- javascript - 用猫鼬定义模型时allowNull和required有什么区别
- c - 当矩阵维数不是4的倍数时如何避免AVX2的错误?
- ubuntu - Kafka 无法在 ubuntu 实例上启动,getLocalHost(InetAddress) 返回 ip,并在 ip 的开头附加“ip”
- php - 在“[||]”之后在php中替换字符串