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?
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.
The flow of data is like this:
forum -> site
site -> server
server -> site -> forum
Here is the code used to create this:
forum
- Discourse Connect Url https://MY-SITE.com/forum-sign-in
site
- page visit handler This part should go on your website when people visit /forum-sign-in
. It will automatically trigger the sso flow once they are signed in, which will happen automatically if they have previously signed in.
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}`)
})
server
- auth handler I'm using firebase functions here, but as long as you have access to firebase-admin
you can use whatever backend you like. Also this example includes some firestore stuff for usernames etc, which is not required.
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
}
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. If this isn't the case for you, check out this discussion and this GitHub gist . The credentials are all the same, the only difference is how they are passed around. It is something like this:
site -> server
server -> site
forum -> server
server -> forum
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.