After a lengthy discussion with @rob and @dthyresson (thanks both!) I managed to finish my auth service.
TL;DR - cookie sessions from dbAuth, but instead of username/password I do an OAuth exchange with a 3rd party OAuth provider. Wanted to share here in case it helps anyone trying to massage dbAuth to their specific needs.
Redwood v0.37.1
Step one is replacing the login function from dbAuth
with a redirect to the OAuth authority
import { AuthProvider } from '@redwoodjs/auth'
import { login } from 'src/auth/client'
// web/src/App.js
const dbAuth = new AuthProvider({ type: 'dbAuth' })
dbAuth.logIn = login // Replace with our OAuth redirect
const CustomDbAuth = () => dbAuth
CustomDbAuth.prototype = React.Component.prototype
const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
<CustomDbAuth type="dbAuth">
// ...
)
Now the redwoodjs useAuth logIn
function will fetch the url and redirect the user
// web/src/auth/client.js
export const login = async ({ redirectTo }) => {
redirectTo && saveRedirectTo(redirectTo)
const response = await global.fetch(
`${global.__REDWOOD__API_PROXY_PATH}/graphql`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
query: LOGIN_URL_QUERY,
}),
}
)
if (response.ok) {
// Redirect user
const { data } = await response.json()
window.location = data.loginUrl.url
}
}
When the user returns, we trigger the dbAuth login. Instead of username/password, we provide code
and state
to complete the OAuth flow.
// web/src/components/Redirect.js
const submitOauthCodeGrant = async () => {
// Call the dbAuth login function
const response = await fetch(`${global.__REDWOOD__API_PROXY_PATH}/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, state, method: 'login' }),
}).then((res) => res.json())
if (response.error) return setErrorMessage(response.error)
if (response.id) window.location.href = getRedirectTo() || '/'
}
React.useEffect(() => {
checkValidParams()
submitOauthCodeGrant()
}, [])
Now in the api, we must replace the login function in our DbAuthHandler. Instead of consuming username/password, it performs the OAuth code grant.
// api/src/functions/auth.js
import { handleOauthCodeGrant } from 'src/lib/oAuth/oAuth'
const authHandler = new DbAuthHandler(event, context, {
const loginOptions = {
// Normal stuff here....
}
})
// Replace the login with our own OAuth logic
authHandler.login = async () => {
const { type, code, state } = this.params
// Do Oauth and create a new user in our database
const user = await handleOauthCodeGrant({ state, code })
const sessionData = { id: user[this.options.authFields.id] }
// TODO: this needs to go into graphql somewhere so that each request makes
// a new CSRF token and sets it in both the encrypted session and the
// csrf-token header
const csrfToken = DbAuthHandler.CSRF_TOKEN
return [
sessionData,
{
'csrf-token': csrfToken,
...this._createSessionHeader(sessionData, csrfToken),
},
]
}
return await authHandler.invoke()
Success! We’ve saved a session cookie, which gets passed in the graphql requests.
Discussion
We need more on Implement CSRF checking with dbAuth · Issue #3075 · redwoodjs/redwood · GitHub . Happy to put a
bounty on this if someone wants to work on it!