What is the proper way to dynamically change the headers in Apollo SubscriptionClient based on the users role in Hasura?
Stack:
The goal here is to utilize Hasura roles for permissions. I know the JWT token has the allowed roles, but I want the ability to set the role based on the users assigned role. The path I am going down is querying Hasura user table for the role with the userID from auth0 via NextJS internal API.
ApolloClient.js
import fetch from 'isomorphic-unfetch'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import { onError } from 'apollo-link-error'
import { WebSocketLink } from 'apollo-link-ws'
import { SubscriptionClient } from 'subscriptions-transport-ws'
let accessToken, role, user = null
const requestAccessToken = async () => {
if (accessToken) return
const res = await fetch(`${process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI}/api/session`)
if (res.ok) {
const json = await res.json()
accessToken = json.accessToken
} else {
accessToken = 'public'
}
}
const requestRole = async (userId) => {
if (role) return
const res = await fetch(`${process.env.NEXT_PUBLIC_APP_HOST}/api/role/"${userId}"`)
if (res.ok) {
const json = await res.json()
console.log(json)
role = json.data.vknursery_users_by_pk.role
}
}
const requestUser = async () => {
if (role) return
const res = await fetch(`${process.env.NEXT_PUBLIC_APP_HOST}/api/me`)
if (res.ok) {
const json = await res.json()
user = json
}
}
// remove cached token on 401 from the server
const resetTokenLink = onError(({ networkError }) => {
if (networkError && networkError.name === 'ServerError' && networkError.statusCode === 401) {
accessToken = null
}
})
const createHttpLink = (headers) => {
const httpLink = new HttpLink({
uri: `https://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`,
credentials: 'include',
headers, // auth token is fetched on the server side
fetch,
})
return httpLink;
}
const createWSLink = () => {
return new WebSocketLink(
new SubscriptionClient(`wss://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`, {
lazy: true,
reconnect: true,
connectionParams: async () => {
await requestAccessToken() // happens on the client
await requestUser()
await requestRole(user.sub) //get role from hasura to assign in apollo request headers
return {
headers: {
'X-Hasura-Role': role,
authorization: accessToken ? `Bearer ${accessToken}` : '',
},
}
},
})
)
}
export default function createApolloClient(initialState, headers) {
const ssrMode = typeof window === 'undefined'
let link
if (ssrMode) {
link = createHttpLink(headers) // executed on server
} else {
link = createWSLink() // executed on client
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
})
}
/api/role/[userId].js
import fetch from 'isomorphic-unfetch';
export default async function me(req, res) {
const {
query: { userId },
} = req
try {
await fetch(`https://${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/graphql`, {
method: 'POST',
headers: {
'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query: `{ vknursery_users_by_pk( auth0_id:${userId}){ role } }` })
})
.then(r => r.json())
.then(data => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
console.log(JSON.stringify(data));
res.end(JSON.stringify(data))
});
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}
/api/me.js
import auth0 from '../../lib/auth0'
export default async function me(req, res) {
try {
await auth0.handleProfile(req, res)
} catch (error) {
console.error(error)
res.status(error.status || 500).end(error.message)
}
}
The role goes either as a join with the users table (you can keep the auth0 id here) or as part of the JWT signed by auth0 (there's a Roles feature in its UI) or as part of an authentication hook (see Hasura's docs). Roles aren't given by the calling client, because normally you don't control its execution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With