hasura / graphql-engine

Blazing fast, instant realtime GraphQL APIs on your DB with fine grained access control, also trigger webhooks on database events.
https://hasura.io
Apache License 2.0
31.2k stars 2.77k forks source link

Allow unsigned (alg: none) JWT tokens #6338

Open siddhatiwari opened 3 years ago

siddhatiwari commented 3 years ago

I'm using the firebase auth emulator for local development which produces unsigned tokens. I'm running the firebase auth emulator and hasura (v1.3.3) locally using docker. It seems that hasura views the unsigned tokens using the recommended HASURA_GRAPHQL_JWT_SECRET for firebase as invalid. When I remove the HASURA_GRAPHQL_JWT_SECRET, all requests are defaulted to the anonymous role, which doesn't represent the actual role of the user from the unsigned token.

Is there a flag to allow using unsigned JWT tokens for development purposes? Or am I missing something with my configuration?

Hasura has been a major productivity boost for me! Just having this small issue setting up my local environment

tirumaraiselvan commented 3 years ago

What do you mean by unsigned tokens? Do you mean the algo is none?

marksyzm commented 3 years ago

I have this issue also with the emulator. It keeps getting back with Could not verify JWT: JWSError JWSNoSignatures

marksyzm commented 3 years ago

I'm using 1.3.2. This is an issue for me as I want to test my cloud functions with actions as I'm working but I would have to make use of live authentication instead.

Edit: Thinking about it, I can make do with live auth for now.

marksyzm commented 3 years ago

I'm limited by live auth as it happens... in firebase I can run all via its emulator but as I have to use a key or jwk_url it just breaks. This is less than ideal as I have to extract the JWT data with 3rd party software. I would prefer to be able to use firebaseAdmin.auth().verifyIdToken(idToken) for example.

jamesknelson commented 3 years ago

@tirumaraiselvan Yes, it appears the algorithm is none, which doesn't appear to be specified in the JWT standard, but it'd be super handy if Hasura could support. This would allow for completely local development - without touching a remote auth server at all.

Here's a dump of one of the tokens produced by the mentioned auth emulator:

Headers:

{
  "alg": "none",
  "typ": "JWT"
}

Full payload:

{
  "aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
  "iat": 1609857856,
  "exp": 1609861456,
  "iss": "firebase-auth-emulator@example.com",
  "sub": "firebase-auth-emulator@example.com",
  "uid": "yRdKtVuhWFShAUm43oT9HhpGMNid",
  "claims": {
    "https://hasura.io/jwt/claims": {
      "...": "..."
    }
  }
}

Given that the token has an issuer and audience, it may be possible to allow a "type" of "none" only when one (or both) of these are specified.

marksyzm commented 3 years ago

I would add an environment variable for that circumstance too so it can only be used in testing and development

jamesknelson commented 3 years ago

@marksyzm I think it should be enough for the existing HASURA_GRAPHQL_JWT_SECRET environment variable to support a none type

marksyzm commented 3 years ago

Yeah probably; save us having user error issues

whollacsek commented 3 years ago

Is there any solution around this?

@tirumaraiselvan the firebase doc section: https://firebase.google.com/docs/emulator-suite/connect_auth#id_tokens

Rykuno commented 3 years ago

I also just ran into this issue. I would agree in having the none option on Hasura's JWT secret for local testing and development. Does anyone know a work around for this in the meanwhile?

Update Here is my current work around. I'm using Apollo with NextJS and I just added some middleware to handle the checking of environments. If the environment is local(where I'm using firebase emulator), it signs the token on request.

// Generate a signed token for the request
// since firebase auth emulator tokens are unsigned
const getLocallySignedToken = token => {
  return jwt.sign(jwt.decode(token), "secret");
};

// condition for signing the token
const isLocalEnvironment = () => {
  return location.hostname === "localhost";
};

//middleware to apply signed token on request
const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  const token = getAuthToken();
  operation.setContext(() => ({
    headers: token
      ? {
          Authorization: `Bearer ${
            isLocalEnvironment() ? getLocallySignedToken(token) : token
          }`
        }
      : { "X-Hasura-Role": `anonymous` }
  }));
  return forward(operation);
});

Remember to set the secret to use a key instead of the firebase JWK

HASURA_GRAPHQL_JWT_SECRET='{"type":"HS256", "key": "secret"}'

I would still LOVE a type of none on the HASURA_GRAPHQL_JWT_SECRET though :).

marksyzm commented 3 years ago

That will help for now, thanks... It isn't great having such a public work around in the code of course but hey, it'll be enough for the time being

HosseinYousefi commented 2 years ago

Remember to set the secret to use a key instead of the firebase JWK

HASURA_GRAPHQL_JWT_SECRET='{"type":"HS256", "key": "secret"}'

This causes errors and I can't run the docker-compose. Normal Firebase JWT secret does work though. Odd!

Update: Nevermind, my "secret" was exactly "secret" which is not long enough! From the docs:

``key``
^^^^^^^
- In case of symmetric key (i.e. HMAC based key), the key as it is. (e.g. -
  "abcdef..."). The key must be long enough for the algorithm chosen,
  (e.g. for HS256 it must be at least 32 characters long).
- In case of asymmetric keys (RSA, EdDSA etc.), only the public key, in a PEM encoded
  string or as a X509 certificate.
klondikedragon commented 2 years ago

In case anyone is using flutter/dart, here is some code that uses the jwt_decoder and jaguar_jwt packages to sign tokens returned by the firebase emulator and have them be trusted by the local instance of Hasura:

String _calcJwtForHasura(String token) {
  if (kDebugMode) {
    try {
      var decoded = JwtDecoder.decode(token);
      var claims = JwtClaim.fromMap(decoded, defaultIatExp: false);
      return issueJwtHS256(
          claims, 'somerandompasswordthatmatcheswhatisinHASURA_GRAPHQL_JWT_SECRET');
    } catch (e) {
      print("Got unexpected exception (will return unmodified token): $e");
      return token;
    }
  } else {
    return token;
  }
}
Future<String> calcJwtForHasura(String token) async {
  return await compute(_calcJwtForHasura, token);
}
nathikazad commented 2 years ago

love you @klondikedragon, if I was a woman, I would have had your kids.

hixus commented 1 year ago

I also just ran into this issue. I would agree in having the none option on Hasura's JWT secret for local testing and development. Does anyone know a work around for this in the meanwhile?

Update Here is my current work around. I'm using Apollo with NextJS and I just added some middleware to handle the checking of environments. If the environment is local(where I'm using firebase emulator), it signs the token on request.

// Generate a signed token for the request
// since firebase auth emulator tokens are unsigned
const getLocallySignedToken = token => {
  return jwt.sign(jwt.decode(token), "secret");
};

// condition for signing the token
const isLocalEnvironment = () => {
  return location.hostname === "localhost";
};

//middleware to apply signed token on request
const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  const token = getAuthToken();
  operation.setContext(() => ({
    headers: token
      ? {
          Authorization: `Bearer ${
            isLocalEnvironment() ? getLocallySignedToken(token) : token
          }`
        }
      : { "X-Hasura-Role": `anonymous` }
  }));
  return forward(operation);
});

Remember to set the secret to use a key instead of the firebase JWK

HASURA_GRAPHQL_JWT_SECRET='{"type":"HS256", "key": "secret"}'

I would still LOVE a type of none on the HASURA_GRAPHQL_JWT_SECRET though :).

Yeah, have been using similar workaround. One thing to note is that jsonwebtoken library is not intended for browsers. Latest version does not work and old versions add about 150kb nodejs polyfills https://github.com/auth0/node-jsonwebtoken/issues/885#issuecomment-1423983985

krisbaum74 commented 6 months ago

I also just ran into this issue. I would agree in having the none option on Hasura's JWT secret for local testing and development. Does anyone know a work around for this in the meanwhile?

Update Here is my current work around. I'm using Apollo with NextJS and I just added some middleware to handle the checking of environments. If the environment is local(where I'm using firebase emulator), it signs the token on request.

// Generate a signed token for the request
// since firebase auth emulator tokens are unsigned
const getLocallySignedToken = token => {
  return jwt.sign(jwt.decode(token), "secret");
};

// condition for signing the token
const isLocalEnvironment = () => {
  return location.hostname === "localhost";
};

//middleware to apply signed token on request
const authMiddleware = new ApolloLink((operation, forward) => {
  // add the authorization to the headers
  const token = getAuthToken();
  operation.setContext(() => ({
    headers: token
      ? {
          Authorization: `Bearer ${
            isLocalEnvironment() ? getLocallySignedToken(token) : token
          }`
        }
      : { "X-Hasura-Role": `anonymous` }
  }));
  return forward(operation);
});

Remember to set the secret to use a key instead of the firebase JWK

HASURA_GRAPHQL_JWT_SECRET='{"type":"HS256", "key": "secret"}'

I would still LOVE a type of none on the HASURA_GRAPHQL_JWT_SECRET though :).

Great info thanks! @Rykuno

i have done similar and got it to work although had to create an endpoint to sign the JWT's as couldnt find a web front end token signer that would do the job and my code for token refreshing is client side.