Auth enabled Express Server with PostGraphile and PostgreSQL
Note: This project is mostly taken from the wonderful PostGraphile tutorial.
A minimal authentication and authorization enabled Express server with PostGraphile middleware creating a GraphQL server from a PostgreSQL schema.
See the this branch for an integration of email activation. This workflow creates users that are not "activated" until they provide their activation code from their email.
git clone https://github.com/tobymurray/postgraphile-login.git
yarn
or npm install
docker run --restart=always -p 5432:5432 --name postgres -e POSTGRES_PASSWORD=password -d postgres:alpine
sudo apt install postgresql-client
.env
file with the relevant connection details
provision.sql
provision.sql
into your PostgreSQL server
psql -h localhost -U postgres -f provision.sql
npm start
mutation {
registerUser(input: {
firstName: "Genghis"
lastName: "Khan"
email: "[email protected]"
password: "Genghis1162"
}) {
user {
id
firstName
lastName
createdAt
}
}
}
{
"data": {
"registerUser": {
"user": {
"id": 2,
"firstName": "Genghis",
"lastName": "Khan",
"createdAt": "2017-06-11T06:17:39.084578"
}
}
}
}
mutation {
authenticate(input: {
email: "[email protected]"
password: "Genghis1162"
}) {
jwt
}
}
{
"data": {
"authenticate": {
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXV0aF9hdXRoZW50aWNhdGVkIiwidXNlcl9pZCI6MiwiaWF0IjoxNDk3MTYyMTIyLCJleHAiOjE0OTcyNDg1MjIsImF1ZCI6InBvc3RncmFwaHFsIiwiaXNzIjoicG9zdGdyYXBocWwifQ.hLZ7p3vJs3UYW9IKB7u8tbXONUl_tZoWhiAAD1-OPQg"
}
}
}
currentUser
is protected, so query thatquery {
currentUser{
id
firstName
lastName
createdAt
}
}
{
"errors": [
{
"message": "unrecognized configuration parameter \"jwt.claims.user_id\"",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"currentUser"
]
}
],
"data": {
"currentUser": null
}
}
jwt
field in the authenticate
response in step 5.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXV0aF9hdXRoZW50aWNhdGVkIiwidXNlcl9pZCI6MSwiaWF0IjoxNDk3MTYwNzA3LCJleHAiOjE0OTcyNDcxMDcsImF1ZCI6InBvc3RncmFwaHFsIiwiaXNzIjoicG9zdGdyYXBocWwifQ.aInZvEVhhDfi9yQDWRzvmSaE7Mk2PufbBrY3rxGlEt8
Bearer
on the right side of the header, otherwise you'll likely see Authorization header is not of the correct bearer scheme format.
query {
currentUser{
nodeId
id
firstName
lastName
createdAt
}
}
{
"data": {
"currentUser": {
"nodeId": "WyJ1c2VycyIsMl0=",
"id": 2,
"firstName": "Genghis",
"lastName": "Khan",
"createdAt": "2017-06-11T06:17:39.084578"
}
}
}
mutation {
updateUser(input: {
nodeId: "WyJ1c2VycyIsMl0="
userPatch: {
lastName: "NotKhan"
}
}) {
user {
nodeId
id
firstName
lastName
createdAt
}
}
}
{
"data": {
"updateUser": {
"user": {
"nodeId": "WyJ1c2VycyIsMl0=",
"id": 2,
"firstName": "Ghengis",
"lastName": "NotKhan",
"createdAt": "2017-06-11T06:17:39.084578"
}
}
}
}
mutation {
registerUser(input: {
firstName: "Serena"
lastName: "Williams"
email: "[email protected]"
password: "NotGhengis"
}) {
user {
nodeId
id
firstName
lastName
createdAt
}
}
}
nodeId
mutation {
updateUser(input: {
nodeId: "WyJ1c2VycyIsM10="
userPatch: {
lastName: "KhanMaybe?"
}
}) {
user {
nodeId
id
firstName
lastName
createdAt
}
}
}
{
"errors": [
{
"message": "No values were updated in collection 'users' using key 'id' because no values were found.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"updateUser"
]
}
],
"data": {
"updateUser": null
}
}
Running the server on this branch for the first time will prompt you to integrate with Gmail. Subsequent times, your client key should be cached. Once Gmail integration is set up, create a user with a real email address you control.
mutation {
registerUser(input: {
firstName: "Firstname"
lastName: "Lastname"
email: "[email protected]"
password: "doesNotMatter"
}) {
user {
id
firstName
lastName
createdAt
}
}
}
There's nothing particularly notable about the response here, so you can ignore it.
mutation {
activateUser(input: {
email: "[email protected]",
activationCode: "00000000-0000-0000-0000-000000000000"
}) {
boolean
}
}
Observe the response:
{
"data": {
"activateUser": {
"boolean": false
}
}
}
mutation {
activateUser (input:{
email: "[email protected]",
activationCode: "e0df9b6b-ef0f-417c-823a-6e871f5c7d43"
}) {
boolean
}
}
And observe the successful activation!
{
"data": {
"activateUser": {
"boolean": true
}
}
}
Move or remove the .env
file and add .env
to the .gitignore
, then bring your .env
back. This will ensure your environment variables (in particular your application server secret) are not added to version control and ultimately shared.
I like to build largely disposable web apps in my spare time, and almost every one needs authentication and authorization to be at all usable. Auth is hard and boring and generally not value added, so I plan on using this as something of a seed for weekend projects.