繁体   English   中英

带有 Firebase 的话语 SSO(话语连接)

[英]Discourse SSO with Firebase (Discourse Connect)

我有一个使用 firebase 身份验证的网站,我想使用这些相同的登录详细信息进行讨论,实现此目的的最佳方法是什么?

1. 使用重定向

这是我发现最优雅的工作流程。 只要您在主站点上登录,它就允许通过单击登录,因为 firebase 身份验证将在访问时自动登录。

数据流是这样的:

  1. forum -> site
    • 发送话语 SSO 请求
  2. site -> server
    • 发送用户令牌和 Discourse SSO 请求
  3. server -> site -> forum
    • 使用 Discourse Login 发送重定向到论坛

这是用于创建它的代码:

  1. forum - 话语连接 Url
https://MY-SITE.com/forum-sign-in
  1. site页面访问处理程序

当人们访问/forum-sign-in时,这部分应在您的网站上显示为 go。 一旦他们登录,它将自动触发 sso 流,如果他们之前已经登录,这将自动发生。

auth.onAuthStateChanged(async user => {
    if (!user)
//this means user was not already signed in. 
//allow the user to sign in like normal
//this callback will be called again once they do
        return
//generate firebase auth token
    const idToken = await user.getIdToken(true)
//get the search params that discourse sent
//if you're in react you should probably do useSearchParams or something
    const params = new URLSearchParams(document.location.search)
    const discoursePayload = params.get('sso')
    const discourseSignature = params.get('sig')
    const response = await fetch(`https://MY-BACKEND.com/discourseAuth`, {
        method: 'POST', //sending a json payload
        mode: 'cors', //work with another domain
        cache: 'no-cache', //send every time
        headers: { 'Content-Type': 'application/json' }, //sending a json payload
        body: JSON.stringify({
            discoursePayload,
            discourseSignature,
            idToken 
        })
    })
    const json = await response.json()
    if (json?.redirectUrl)
//lets go back to forum with credentials in the redirectUrl
//we're doing a custom redirect instead of a redirect-follow which is easier for security
        window.location.replace(json.redirectUrl)
    else 
//something went wrong
        throw new Error(`Redirect URL not found in response - ${response.status} - ${json}`)
})
  1. server - 身份验证处理程序

我在这里使用 firebase 函数,但只要您有权访问firebase-admin ,您就可以使用您喜欢的任何后端。 此示例还包括一些用于用户名等的 Firestore 内容,这不是必需的。

import DiscourseSSO from 'discourse-sso'
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'

const auth = admin.auth()
const firestore = admin.firestore()
const discourse = new DiscourseSSO(ssoSecret)


export const discourseAuth = functions.https.onRequest((req, res) => {
    if (!handleCors(req, res))
        return

    //1. validate discourse payload
    const { idToken, discoursePayload, discourseSignature } = req.body
    if (!discourse.validate(discoursePayload, discourseSignature))
        return res.status(401).send(`Bad Discourse Payload: ${origin}`)

    //2. validate user
    const decodedClaims = await auth.verifyIdToken(idToken).catch()
    if (!decodedClaims)
        return res.status(401).send(`Bad Id Token: ${idToken}`)
    const { uid } = decodedClaims
    const user = await auth.getUser(uid).catch()
    if (!user)
        return res.status(401).send(`User Not Found: ${uid}`)

    //3. get user firestore (optional)
    const firestoreDoc = await firestore.collection('users').doc(uid).get()
    const userData = firestoreDoc.data()

    //4. build discourse auth body
    const q = discourse.buildLoginString({
        nonce: discourse.getNonce(discoursePayload),
        external_id: uid,
        email: user.email,
        //optional
        name: userData.displayName,
        username: userData.username,
        avatar_url:userData.avatar
    })
    const redirectUrl = `$https://forum.MY-SITE.com/session/sso_login?${q}`
    res.status(200).send(JSON.stringify({ redirectUrl }))

const handleCors = (req, res) => {
    const origin = req.headers.origin || req.header('origin')
    if(origin != 'https://MY-SITE.com'){
        res.status(401).send(`Bad Origin: ${origin}`)
        return false
    }
    res.set('Access-Control-Allow-Origin', origin)
    res.set('Access-Control-Allow-Credentials', 'true')
    res.set('Access-Control-Allow-Headers', ['Content-Type'])
    if (req.method === 'OPTIONS'){
        res.status(200).end()
        return false
    }
    return true
}

2.使用Cookies

第二种方法是我遇到问题的方法,因为我的服务器与客户端和论坛位于不同的域中。 这意味着 Safari 会将 cookies 视为第三方,并在尝试传递它们时给您带来困难。 如果您不是这种情况,请查看此讨论和此GitHub 要点 凭证都是一样的,唯一的区别是它们是如何传递的。 它是这样的:

  1. site -> server
    • 发送 Firebase 用户令牌
  2. server -> site
    • 设置 session cookie
  3. forum -> server
    • 发送 cookie 和话语 SSO 请求
  4. server -> forum
    • 发送话语登录

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM