Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

postgraphile docs: v5 JWT guide is lacking #2372

Open
jemgillam opened this issue Feb 17, 2025 · 0 comments
Open

postgraphile docs: v5 JWT guide is lacking #2372

jemgillam opened this issue Feb 17, 2025 · 0 comments

Comments

@jemgillam
Copy link
Contributor

Summary

Our documentation around JWTs is lacking in version 5 docs.

From @benjie:

We removed JWT verification by default in V5, but it’s added either by the v4 preset or by the lazy-jwt preset. We’re hoping people will do their own JWT to cover refresh tokens and the like, also I loathe JWT

More information can be found here: https://postgraphile.org/postgraphile/next/config/#making-http-data-available-to-plan-resolvers

Also why I don’t like JWT for this: https://sprout.io/Benjie/13938-why-i-dont-use-jwt-for-sessions

Discord Discussion

From our community Discord user rohoon:

I'm trying out V5 and am having a problem getting the Authorization header to be recognised. The schema I have works perfectly in V4.

My authorize mutation is producing a JWT that looks fine when I decode it, but when I add the token in the Authorization header (tried both Ruru and Insomnia), the claims aren't visible in my queries and my current_user_account_id function is returning null.

Image

Here is my graphile.config.js:

import { PostGraphileAmberPreset } from "postgraphile/presets/amber";

/** @type {GraphileConfig.Preset} */
const preset = {
    extends: [PostGraphileAmberPreset],
    gather: {
        pgJwtTypes: "public.jwt_token",
    },
    schema: {
        pgJwtSecret: process.env.JWT_SECRET,
    },
};

export default preset;

And my authentication SQL, which works fine in V4:


-- Gets the current user ID.
CREATE OR REPLACE FUNCTION current_user_account_id() RETURNS UUID
  AS $$ SELECT current_setting('jwt.claims.user_id', true)::UUID $$
  LANGUAGE SQL STABLE;

-- Gets the current user role.
CREATE OR REPLACE FUNCTION current_user_account_role() RETURNS TEXT
  AS $$ SELECT current_setting('jwt.claims.role', true)::TEXT $$
  LANGUAGE SQL STABLE;

-- Defines the structure of JWTs used for authentication.
DROP TYPE IF EXISTS jwt_token CASCADE;
CREATE TYPE jwt_token AS (
  username   VARCHAR(64),
  role       VARCHAR(32),
  first_name VARCHAR(64),
  last_name  VARCHAR(64),
  user_id    UUID,
  exp        INTEGER
);

-- Authenticates a user with the given username and password.
CREATE OR REPLACE FUNCTION authenticate(
  username TEXT,
  password TEXT
) RETURNS jwt_token AS $$
DECLARE
  login private.login;
  found_user user_account;
BEGIN
  SELECT * INTO login
    FROM private.login
    WHERE private.login.provider = 'JWT'
    AND private.login.username = authenticate.username;

  SELECT * INTO found_user
    FROM user_account
    WHERE user_account.id = login.user_account_id;

  IF login.password_hash = crypt(password, login.password_hash) THEN
    RETURN (
      login.username,
      found_user.role,
      found_user.first_name,
      found_user.last_name,
      login.user_account_id,
      extract(epoch from now() + interval '7 days')
    )::jwt_token;
  ELSE
    RAISE EXCEPTION 'Invalid credentials' USING ERRCODE = 'invalid_password';
  END IF;
END;
$$ LANGUAGE PLPGSQL STRICT SECURITY DEFINER;

I've also set the JWT_SECRET variable (just to "secret" for testing).
I just tried the same thing with pretty much the exact code from the docs and got the same result.

create extension if not exists pgcrypto;

drop table if exists person_account;
create table person_account (
  id serial primary key,
  email text not null unique,
  password_hash text not null,
  is_admin boolean not null default false
);
insert into person_account (email, password_hash, is_admin) values ('[email protected]', crypt('admin', gen_salt('bf')), true);

drop type if exists jwt_token cascade;
create type jwt_token as (
  role text,
  exp integer,
  person_id integer,
  is_admin boolean,
  username varchar
);

create or replace function authenticate(
  email text,
  password text
)
returns jwt_token
as $$
declare
  account person_account;
begin
  select a.* into account
    from person_account as a
    where a.email = authenticate.email;

  if account.password_hash = crypt(password, account.password_hash) then
    return (
      'person_role',
      extract(epoch from now() + interval '7 days'),
      account.id,
      account.is_admin,
      account.email
    )::jwt_token;
  else
    return null;
  end if;
end;
$$ language plpgsql strict security definer;

create or replace function current_user_id() returns integer as $$
  select nullif(current_setting('jwt.claims.user_id', true), '')::integer;
$$ language sql stable;

I just tried the exact same setup as above with the V4 CLI and it works fine:

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 🌳 Triage
Development

No branches or pull requests

1 participant