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

how to use as auth service with hasura #250

Closed
chenkaiC4 opened this issue May 31, 2021 · 8 comments
Closed

how to use as auth service with hasura #250

chenkaiC4 opened this issue May 31, 2021 · 8 comments
Assignees
Labels

Comments

@chenkaiC4
Copy link

❓ Questions and Help

hi, this project is very nice~
now, i have deploy a standlone Hasura and also Supertokens with dockers.
the Supertokens and Hasura works well. But i want to use Supertokens as auth service for Hasura through jwt or jws。
from docs of Hasura, hasura just work as a graphql api service, and apis are auth by jwt/jws with roles set in payload.

the logic i want to use is:

  1. set jwt payload required by hasura, at follow code in supertokens session feature.
sessionFeature: {
   setJwtPayload: async (user, context, action) => {
        // This is called post sign in and sign up
       return {
          'x-hasura-default-role': 'admin',
          'x-hasura-allowed-roles': ['admin']
        };
   }
}
  1. request to hasura with jwt token at header
{
  {
  "type": "HS256",  // what sine
  "key": "my_long_secret",
  "jwk_url": "<optional-url-to-refresh-jwks>",
  "claims_namespace": "<optional-key-name-in-claims>",
  "claims_namespace_path":"<optional-json-path-to-the-claims>",
  "claims_format": "json|stringified_json",
  "header": "<optional-key-to-indicate-cookie-or-authorization-header>"
  }
}
  1. set hasura jwt settings

image

@chenkaiC4
Copy link
Author

i found the jwt or session of supertokens is dynamic? i don't find the secret as need for jwt sign. how to give one ?becuase hasura need one, here is my hasura docker file:

{
  "type": "HS256",
  "key": "my_long_long_secret",
  "claims_namespace": "supertokens",
  "claims_format": "json",
  "header": { "type": "Authorization" }
}

the config tell hasura, use HS256 algo and key is my_long_long_secret to decode jwt token at Authorization(start as Berear)

@chenkaiC4
Copy link
Author

yeah, hasura also accept a jws url, but what the url for jws?

@chenkaiC4
Copy link
Author

@chenkaiC4
Copy link
Author

chenkaiC4 commented May 31, 2021

from now, what i have done and not know how to:

  • run both service standlone by docker
  • a simple jwt signed by jwt.io at here, this jwt token woke well when i add to query header {'Authorization':Bearer ${token}}. tips: take a look at the Algorithm and VERIFY SIGNATURE at jwt.io.
  • confirm the token signed by supertokens will auto add to query header
  • add custom jwt payload at supertokens
  • supertokens: how to sign a jwt token with Algorithm: hs256 and a secret such as long_long_long_secret

now, supertokens will sign a session, and the jwt payload can be add to it. but what is still not clear to me is how to get a jwt token like this demo

@gusfune
Copy link

gusfune commented May 31, 2021

Hi @chenkaiC4 just a quick heads-up from integrating with Hasura, but because Supertokens has dynamic JWKs, it means that this setup won't work with Hasura. You can disable the rotation of keys, but you'd be dropping out a strong security advantage of super tokens. So far the solution would be using a webhook to take full advantage of super tokens, but the downside is you have to keep a separate server for it, such as hasura-supertokens which I am a maintainer.

@rishabhpoddar
Copy link
Contributor

rishabhpoddar commented Jun 7, 2021

Apart from the webhook method provided by @gusfune , here is another approach that doesn't involve creating a webhook, and allows querying Hasura directly from the frontend. This method is coming soon...

First, some terminology:

  • A "SuperTokens access token (SuAT)" is a JWT that is created on login / sign up or call to createNewSession. This is what is verified when we call the verifySession middleware. The private / public keys for this are entirely managed by the supertokens core and is rotated from time to time. This token can contain an arbitrary JWT payload as defined by the user.
  • A "Secondary JWT (SeJWT)" is a JWT that contains some an expiry and some arbitrary payload. This follows the JWT spec, is signed by a private key, and can be verified by a jwks.json endpoint. This token can be sent to Hasura for auth (from the backend or the frontend).

Implementation details:

Note that this will not be the default implementation of createNewSession. It will be an override (new feature coming soon) that can be applied to the default behaviour

createNewSession(userId, jwtPayload) {
   let SeJWT = createSeJWT(jwtPayload);

   // we then modify the jwtPayload to contain the SeJWT
   jwtPayload = {
      sejwt: SeJWT
   };
   let SuAT = createAccessTokenFromSuperTokenCore(userId, jwtPayload);
   
   // attach SuAT to cookies etc...
}
  • We can see that the SeJWT is "inside" the SuAT. The JWT Payload that can be sent to the createNewSession function can contain the userId or user role etc.. that is needed by Hasura.
  • The createSeJWT function calls some service / API that take a payload, and returns a signed JWT. This service would also expose a jwks.json endpoint that can be used to verify the SeJWT. You would need to supply that jwks.json endpoint to Hasura.
  • When SuAT expires, we all the supertokens' refresh API, which will then generate a new SeJWT and SuAT (in a similar way to the above). The lifetime of SeJWT will be just a little bit more than the lifetime of SuAT so that SeJWT is always alive whenever SuAT is alive.

Getting the Hasura token (SeJWT) in the backend

app.post("/some-api", Session.verifySession(), async (req, res) => {
   let session = req.session;
   let hasuraToken = session.getJWTPayload()["sejwt"];

   // query Hasura with hasuraToken
});

Getting the Hasura token on the frontend

let jwtPayload = await Session.getJWTPayloadSecurely();
let hasuraToken = jwtPayload["sejwt"];

// add hasuraToken to authorisation header when querying Hasura
  • Calling getJWTPayloadSecurely will make sure that the access token is alive. If it isn't it will implicitly refresh the session (generating new SuAT and SeJWT)

Updating contents of the Hasura token (like the user's role)

app.post("/update-role", Session.verifySession(), async (req, res) => {
   let session = req.session;
   let existingJWTPayload = await session.getJWTPayload();
   await session.updateJWTPayload({
      ...existingJWTPayload,
      role: "newRole"
   });
});
  • Post the call to updateJWTPayload, the role in the Hasura token will have been changed (within this API, on the frontend, and for every subsequent API).
  • The one caveat to this is that if another device / session changes the JWTPayload belonging to some other device / session, the second session will not get that change until it is refreshed. If this is an issue, then it's better to use the auth webhook method instead of this one.

JWT service:

  • We will provide one that can be used for this purpose. We will provide two endpoints (per user of ours):
    • One will be used to create a JWT given the payload
    • The other will expose the jwks.json for the private key used to sign your JWT (this can be given to Hasura)
  • Endpoints we provide will be https://api.supertokens.io/....
  • We will also provide a method so that you can create your own JWT given the payload (so that you don't have to use our endpoint at all). However, this would mean you would also need to create a jwks.json endpoint to give to Hasura.

Customisation options

  • To be able to access / expose the Hasura token only on the backend, and not the frontend (so that frontend XSS attacks can't steal the token). If this is set, it of course implies that one will not be able to query Hasura from the frontend directly..
  • What payload to set in the Hasura token during session creation, and to be able to change this payload as well.
  • How the JWT is created - either our service, or your custom implementation.
  • The key to be used in the JWT payload. This will allow multiple uses of this recipe

Advantages:

  • Hasura verification is stateless / quick + fits well into the Hasura auth ecosystem
  • No need to use / maintain an auth webhook
  • Most of the above mentioned implementation details will be hidden away / seamless for ease of developer use.
  • Short loved Hasura tokens that are refreshed regularly.
  • Being able to query Hasura directly from the frontend.

Disadvantages:

  • Delay in changes of payload in Hasura token if they are changed from another session
  • Dependency on a service to create JWTs (though it's easy enough to make one yourself).
  • Dependency on a secret key (for SeJWT). If this key is compromised, an attacker could generate their own SeJWT and use that to query Hasura on behalf of your users. Note that a theft of this secret key will not make your own APIs accessible since those are accessed via SuAT.

@rishabhpoddar rishabhpoddar modified the milestone: JWT signing and JWKS APIs + Hasura integration Jul 2, 2021
@rishabhpoddar
Copy link
Contributor

Perhaps the core can also offer functionality to sign JWTs (using the api-key as the auth method), and provide a jwks endpoint.

@rishabhpoddar
Copy link
Contributor

We have released an implementation of JWT which can be used to integrate with Hasura. Integration docs can be found here: https://supertokens.io/docs/thirdpartyemailpassword/hasura-integration/with-jwt

So closing this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants