hasura/graphql-engine
GitHub で見るdocs: add guide on custom JWT server with express & node
Open
#6,247 opened on 2020年11月24日
c/learnhelp wanteds/wip
説明
A guide on custom JWT server with express & node needs to be added to this PR.
This is the boilerplate code:
import express from "express"
import fetch from "cross-fetch"
import jwt from "jsonwebtoken"
import bcrypt from "bcryptjs"
const app = express()
const PORT = 3000
const HASURA_ENDPOINT = "http://localhost:8080/v1/graphql"
const HASURA_ADMIN_SECRET = "my-secret"
// Don't actually do this, read this from process.env in a real app
const HASURA_GRAPHQL_JWT_SECRET = "XM9RnWahz+qrjSJjG/RNCMTR55AWhj0BKkru9Ksr/rY="
const JWT_EXPIRE_TIME = "60m"
// This is a function which takes the URL and headers for Hasura queries
// and returns a function which sends GraphQL requests to the Hasura instance
const makeGraphQLClient = ({ url, headers }) => async ({
query,
variables,
}) => {
const request = await fetch(url, {
headers,
method: "POST",
body: JSON.stringify({ query, variables }),
})
return request.json()
}
const sendQuery = makeGraphQLClient({
url: HASURA_ENDPOINT,
headers: {
"X-Hasura-Admin-Secret": HASURA_ADMIN_SECRET,
},
})
function generateJWT({ allowedRoles, defaultRole, otherClaims }) {
const payload = {
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": allowedRoles,
"x-hasura-default-role": defaultRole,
...otherClaims,
},
}
return jwt.sign(payload, HASURA_GRAPHQL_JWT_SECRET, {
algorithm: "HS256",
expiresIn: JWT_EXPIRE_TIME,
})
}
/**
* We will turn these REST API endpoints into Hasura Actions so that the client can call them through GraphQL queries within Hasura
* https://hasura.io/docs/1.0/graphql/core/actions/action-handlers.html#http-handler
*
* To do this, we just need to define GraphQL types for:
* - The function's input arguments
* - The function's return type
*
* This can be done in the web console UI in Hasura, under the "Actions" tab
*/
app.post("/api/actions/signup", async (req, res) => {
const user = req.body.input.user
user.password = bcrypt.hashSync(user.password)
const request = await sendQuery({
query: `
mutation InsertUser($user: user_insert_input!){
insert_user_one(object: $user) {
id
}
}`,
variables: { user },
})
const token = generateJWT({
defaultRole: "user",
allowedRoles: ["user"],
otherClaims: {
"x-hasura-user-id": request.insert_user_one.id,
},
})
return res.json({ token })
})
app.post("/api/actions/login", async (req, res) => {
const user = req.body.input.user
const request = await sendQuery({
query: `
query FindUserByEmail($email: String!){
user(where: { email: { _eq: $email } }, limit: 1) {
id
password
}
}`,
variables: { email: user.email },
})
const dbUser = request.user[0]
if (!dbUser) return res.status(400).json({ error: "No user found" })
const validPassword = bcrypt.compareSync(user.password, dbUser.password)
if (!validPassword) return res.status(400).json({ error: "Invalid" })
const token = generateJWT({
defaultRole: "user",
allowedRoles: ["user"],
otherClaims: {
"x-hasura-user-id": dbUser.id,
},
})
return res.json({ token })
})
// Bind to 0.0.0.0 host, so it'll work in Docker too
app.listen(PORT, "0.0.0.0", () => {
console.log(`Server started on http://localhost:${PORT}`)
})
The guide needs to be added here.
The structure can be similar to this.