{"id":13682290,"url":"https://github.com/tobymurray/postgraphile-login","last_synced_at":"2025-04-14T01:22:49.315Z","repository":{"id":42764502,"uuid":"93973696","full_name":"tobymurray/postgraphile-login","owner":"tobymurray","description":"Auth enabled Express Server with PostGraphile and PostgreSQL","archived":false,"fork":false,"pushed_at":"2023-01-07T04:25:26.000Z","size":182,"stargazers_count":56,"open_issues_count":6,"forks_count":8,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T15:21:29.799Z","etag":null,"topics":["authentication","authorization","express","jwt","postgraphile","postgraphql","postgresql"],"latest_commit_sha":null,"homepage":"","language":"PLpgSQL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tobymurray.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-06-11T00:57:12.000Z","updated_at":"2024-05-09T05:57:02.000Z","dependencies_parsed_at":"2023-02-06T11:46:18.458Z","dependency_job_id":null,"html_url":"https://github.com/tobymurray/postgraphile-login","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobymurray%2Fpostgraphile-login","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobymurray%2Fpostgraphile-login/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobymurray%2Fpostgraphile-login/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobymurray%2Fpostgraphile-login/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tobymurray","download_url":"https://codeload.github.com/tobymurray/postgraphile-login/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248805448,"owners_count":21164332,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","authorization","express","jwt","postgraphile","postgraphql","postgresql"],"created_at":"2024-08-02T13:01:43.544Z","updated_at":"2025-04-14T01:22:49.277Z","avatar_url":"https://github.com/tobymurray.png","language":"PLpgSQL","funding_links":[],"categories":["PLpgSQL"],"sub_categories":[],"readme":"Note: This project is mostly taken from the wonderful [PostGraphile tutorial](https://github.com/graphile/postgraphile/blob/v4/examples/forum/TUTORIAL.md#authentication-and-authorization).\n\n# What is it?\n\nA minimal authentication and authorization enabled Express server with PostGraphile middleware creating a GraphQL server from a PostgreSQL schema.\n\n## Email account activation\n\nSee the [this branch](https://github.com/tobymurray/postgraphile-login/tree/feature/email-activation) for an integration of email activation. This workflow creates users that are not \"activated\" until they provide their activation code from their email.\n\n# Get it running\n\n1. Clone this repository\n    - `git clone https://github.com/tobymurray/postgraphile-login.git`\n1. Install dependencies\n    - `yarn` or `npm install`\n1. Ensure you have a PostgreSQL server running somewhere. If you don't, start one.\n    - E.g.: `docker run --restart=always -p 5432:5432 --name postgres -e POSTGRES_PASSWORD=password -d postgres:alpine`\n1. Ensure you have a PostgreSQL client available. If you don't, install one.\n    - E.g.: `sudo apt install postgresql-client`\n1. Fill out the `.env` file with the relevant connection details\n    - Note that if you change values, you may have to update `provision.sql`\n1. Load the contents of `provision.sql` into your PostgreSQL server\n    - E.g.: `psql -h localhost -U postgres -f provision.sql`\n    - NOTE: If you're using docker, you need to specify the host explicilty (PSQL tries the socket by default, which fails)\n1. Start the server\n    - `npm start`\n\n# Try it out\n1. Navigate to Graph\u003ci\u003ei\u003c/i\u003eQL the port you've configured (3000 by default)\n    - e.g. http://localhost:3000/graphiql\n\n## Create a user\n2. Register a user via GraphQL mutation\n    - e.g.\n```\nmutation {\n  registerUser(input: {\n    firstName: \"Genghis\"\n    lastName: \"Khan\"\n    email: \"Genghis@khan.mn\"\n    password: \"Genghis1162\"\n  }) {\n    user {\n      id\n      firstName\n      lastName\n      createdAt\n    }\n  }\n}\n```\n3. Observe the response\n    - e.g.\n```\n{\n  \"data\": {\n    \"registerUser\": {\n      \"user\": {\n        \"id\": 2,\n        \"firstName\": \"Genghis\",\n        \"lastName\": \"Khan\",\n        \"createdAt\": \"2017-06-11T06:17:39.084578\"\n      }\n    }\n  }\n}\n```\n\n# Observe authentication working\n4. Try authenticating with a different GraphQL mutation\n    - e.g.\n```\nmutation {\n  authenticate(input: {\n    email: \"Genghis@khan.mn\"\n    password: \"Genghis1162\"\n  }) {\n    jwt \n  }\n}\n```\n5. Observe the response\n    - e.g.:\n```\n{\n  \"data\": {\n    \"authenticate\": {\n      \"jwt\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXV0aF9hdXRoZW50aWNhdGVkIiwidXNlcl9pZCI6MiwiaWF0IjoxNDk3MTYyMTIyLCJleHAiOjE0OTcyNDg1MjIsImF1ZCI6InBvc3RncmFwaHFsIiwiaXNzIjoicG9zdGdyYXBocWwifQ.hLZ7p3vJs3UYW9IKB7u8tbXONUl_tZoWhiAAD1-OPQg\"\n    }\n  }\n}\n```\n\n## Try making an unauthenticated request when authentication is necessary\n6. `currentUser` is protected, so query that\n```\nquery {\n  currentUser{\n    id\n    firstName\n    lastName\n    createdAt\n  }\n}\n```\n7. Observe the not-particularly-friendly response\n```\n{\n  \"errors\": [\n    {\n      \"message\": \"unrecognized configuration parameter \\\"jwt.claims.user_id\\\"\",\n      \"locations\": [\n        {\n          \"line\": 2,\n          \"column\": 3\n        }\n      ],\n      \"path\": [\n        \"currentUser\"\n      ]\n    }\n  ],\n  \"data\": {\n    \"currentUser\": null\n  }\n}\n```\n\n## Try making an authenticated request when authentication is necessary\n8. You'll need the ability to send your JWT to the server, which unfortunately isn't possible with vanilla Graph\u003ci\u003ei\u003c/i\u003eQL.\n    - If you're in Chrome you can try [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj/related)\n    - If you're in Firefox you can try [Modify Headers](https://addons.mozilla.org/en-US/firefox/addon/modify-headers/)\n    - If you're in another browser, you can try Chrome or Firefox\n9. Set an authorization header by copy/pasting the value out of the `jwt` field in the `authenticate` response in step 5.\n    - `Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXV0aF9hdXRoZW50aWNhdGVkIiwidXNlcl9pZCI6MSwiaWF0IjoxNDk3MTYwNzA3LCJleHAiOjE0OTcyNDcxMDcsImF1ZCI6InBvc3RncmFwaHFsIiwiaXNzIjoicG9zdGdyYXBocWwifQ.aInZvEVhhDfi9yQDWRzvmSaE7Mk2PufbBrY3rxGlEt8`\n    - Don't forget the `Bearer` on the right side of the header, otherwise you'll likely see `Authorization header is not of the correct bearer scheme format.`\n10. Submit the query with the authorization header attached\n```\nquery {\n  currentUser{\n    nodeId\n    id\n    firstName\n    lastName\n    createdAt\n  }\n}\n```\n11. Observe your now successful response\n```\n{\n  \"data\": {\n    \"currentUser\": {\n      \"nodeId\": \"WyJ1c2VycyIsMl0=\",\n      \"id\": 2,\n      \"firstName\": \"Genghis\",\n      \"lastName\": \"Khan\",\n      \"createdAt\": \"2017-06-11T06:17:39.084578\"\n    }\n  }\n}\n```\n# Observe authorization working\n12. With the authorization header set, try updating Genghis\n```\nmutation {\n  updateUser(input: {\n    nodeId: \"WyJ1c2VycyIsMl0=\"\n    userPatch: {\n      lastName: \"NotKhan\"\n    }\n  }) {\n    user {\n      nodeId\n      id\n      firstName\n      lastName\n      createdAt\n    }\n  }\n}\n```\n13. Observe that it works:\n```\n{\n  \"data\": {\n    \"updateUser\": {\n      \"user\": {\n        \"nodeId\": \"WyJ1c2VycyIsMl0=\",\n        \"id\": 2,\n        \"firstName\": \"Ghengis\",\n        \"lastName\": \"NotKhan\",\n        \"createdAt\": \"2017-06-11T06:17:39.084578\"\n      }\n    }\n  }\n}\n```\n14. Add a friend\n```\nmutation {\n  registerUser(input: {\n    firstName: \"Serena\"\n    lastName: \"Williams\"\n    email: \"Serena@Williams.ca\"\n    password: \"NotGhengis\"\n  }) {\n    user {\n      nodeId\n      id\n      firstName\n      lastName\n      createdAt\n    }\n  }\n}\n```\n15. Keeping Genghis' JWT, try modifying your friend\n    - Note this is Serena's `nodeId`\n```\nmutation {\n  updateUser(input: {\n    nodeId: \"WyJ1c2VycyIsM10=\"\n    userPatch: {\n      lastName: \"KhanMaybe?\"\n    }\n  }) {\n    user {\n      nodeId\n      id\n      firstName\n      lastName\n      createdAt\n    }\n  }\n}\n```\n16. Get rejected\n```\n{\n  \"errors\": [\n    {\n      \"message\": \"No values were updated in collection 'users' using key 'id' because no values were found.\",\n      \"locations\": [\n        {\n          \"line\": 2,\n          \"column\": 3\n        }\n      ],\n      \"path\": [\n        \"updateUser\"\n      ]\n    }\n  ],\n  \"data\": {\n    \"updateUser\": null\n  }\n}\n```\n\n# Activate user\n\nRunning the server on [this branch](https://github.com/tobymurray/postgraphile-login/tree/feature/email-activation) 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.\n\n```\nmutation {\n  registerUser(input: {\n    firstName: \"Firstname\"\n    lastName: \"Lastname\"\n    email: \"realemail@gmail.com\"\n    password: \"doesNotMatter\"\n  }) {\n    user {\n      id\n      firstName\n      lastName\n      createdAt\n    }\n  }\n}\n```\n\nThere's nothing particularly notable about the response here, so you can ignore it.\n\n## Activate with the wrong code\n\n```\nmutation {\n  activateUser(input: {\n    email: \"realemail@gmail.com\",\n    activationCode: \"00000000-0000-0000-0000-000000000000\"\n  }) {\n    boolean\n  }\n}\n```\n\nObserve the response:\n\n```\n{\n  \"data\": {\n    \"activateUser\": {\n      \"boolean\": false\n    }\n  }\n}\n```\n\n## Activate with the right code\n\n```\nmutation {\n  activateUser (input:{\n    email: \"realemail@gmail.com\",\n    activationCode: \"e0df9b6b-ef0f-417c-823a-6e871f5c7d43\"\n  }) {\n    boolean\n  }\n}\n```\nAnd observe the successful activation!\n\n```\n{\n  \"data\": {\n    \"activateUser\": {\n      \"boolean\": true\n    }\n  }\n}\n```\n\n## Note if you actually use this\nMove 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.\n\n# Why write this up?\n\nI 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. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftobymurray%2Fpostgraphile-login","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftobymurray%2Fpostgraphile-login","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftobymurray%2Fpostgraphile-login/lists"}