简体   繁体   English

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

[英]Discourse SSO with Firebase (Discourse Connect)

I have a website that uses firebase authentication and I want to use these same login details for discourse, whats the best way to achieve this?我有一个使用 firebase 身份验证的网站,我想使用这些相同的登录详细信息进行讨论,实现此目的的最佳方法是什么?

1. Using Redirects 1. 使用重定向

This is the workflow I have found the most elegant.这是我发现最优雅的工作流程。 It allows sign in from discourse with a single click as long as you are signed in on the main site, because firebase auth will auto-sign-in on visit.只要您在主站点上登录,它就允许通过单击登录,因为 firebase 身份验证将在访问时自动登录。

The flow of data is like this:数据流是这样的:

  1. forum -> site
    • send Discourse SSO Request发送话语 SSO 请求
  2. site -> server
    • send user token and Discourse SSO Request发送用户令牌和 Discourse SSO 请求
  3. server -> site -> forum
    • send redirect to forum with Discourse Login使用 Discourse Login 发送重定向到论坛

Here is the code used to create this:这是用于创建它的代码:

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

This part should go on your website when people visit /forum-sign-in .当人们访问/forum-sign-in时,这部分应在您的网站上显示为 go。 It will automatically trigger the sso flow once they are signed in, which will happen automatically if they have previously signed in.一旦他们登录,它将自动触发 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 - auth handler server - 身份验证处理程序

I'm using firebase functions here, but as long as you have access to firebase-admin you can use whatever backend you like.我在这里使用 firebase 函数,但只要您有权访问firebase-admin ,您就可以使用您喜欢的任何后端。 Also this example includes some firestore stuff for usernames etc, which is not required.此示例还包括一些用于用户名等的 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. Using Cookies 2.使用Cookies

The second method is one I had trouble with because my server is on a different domain from the client and forum.第二种方法是我遇到问题的方法,因为我的服务器与客户端和论坛位于不同的域中。 This means that Safari will treat the cookies as third party and give you a hard time when trying to pass them around.这意味着 Safari 会将 cookies 视为第三方,并在尝试传递它们时给您带来困难。 If this isn't the case for you, check out this discussion and this GitHub gist .如果您不是这种情况,请查看此讨论和此GitHub 要点 The credentials are all the same, the only difference is how they are passed around.凭证都是一样的,唯一的区别是它们是如何传递的。 It is something like this:它是这样的:

  1. site -> server
    • send Firebase user token发送 Firebase 用户令牌
  2. server -> site
    • set session cookie设置 session cookie
  3. forum -> server
    • send cookie and discourse SSO request发送 cookie 和话语 SSO 请求
  4. server -> forum
    • send Discourse Login发送话语登录

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

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