Skip to content
This repository has been archived by the owner on Oct 29, 2021. It is now read-only.

JWT token validation without signing #121

Open
Unisay opened this issue Sep 20, 2018 · 11 comments
Open

JWT token validation without signing #121

Unisay opened this issue Sep 20, 2018 · 11 comments

Comments

@Unisay
Copy link

Unisay commented Sep 20, 2018

In the microservice infrastructure I am developing JWT tokens are issued by microservice A and validated by microservices B and C.
When developing B and C I wan't to configure servant-auth-server to only validate tokens and never sign. Given how JWTSettings data type is defined (signingKey is mandatory) - I don't see how I could achieve my goal:

-- | @JWTSettings@ are used to generate cookies, and to verify JWTs.
data JWTSettings = JWTSettings
  {
  -- | Key used to sign JWT.
    signingKey      :: Jose.JWK
  -- | Algorithm used to sign JWT.
  , jwtAlg          :: Maybe Jose.Alg
  -- | Keys used to validate JWT.
  , validationKeys  :: Jose.JWKSet
  -- | An @aud@ predicate. The @aud@ is a string or URI that identifies the
  -- intended recipient of the JWT.
  , audienceMatches :: Jose.StringOrURI -> IsMatch
  } deriving (Generic)

The questions are:
How is it possible to address aforementioned use-case with the current state of the code,
and if its not possible, would you consider validation-only use-case to be implemented in the future?

@domenkozar
Copy link
Collaborator

signingKey is only used in makeJWT, which you can decide just to never call. So for now you'll need to call defaultJWTSettings with a key to generate the signing key, but it would never be used.

@Unisay
Copy link
Author

Unisay commented Sep 24, 2018

Thank you,
I understand that the approach you suggested would work.
Do you regard it as a long term solution or as a temporary workaround?

(The first think that comes to mind is to model signingKey as Maybe Jose.JWT)

@alpmestan
Copy link
Contributor

I don't want to answer in @domenkozar's name, so this is just my personal opinion: I think we're always open to improvements. So if you have an idea for making this a little less confusing and maybe a little more elegant, we'd be interested in hearing about it. Always. :-)

@ProofOfKeags
Copy link

+1 for this. I think it's a common enough pattern that the service issuing the auth token might be different than the services validating the auth token, in which case you don't want to require the private key being onboard the validating services.

@nmattia
Copy link

nmattia commented Mar 26, 2019

@domenkozar could we add this to the README somewhere?

signingKey is only used in makeJWT, which you can decide just to never call. So for now you'll need to call defaultJWTSettings with a key to generate the signing key, but it would never be used.

that's what I suspected but I'm glad I found this ticket to confirm.

@nmattia
Copy link

nmattia commented Mar 26, 2019

Actually I'm confused again:

(n ~ S (S Z), HasServer (AddSetCookiesApi n api) ctxs, AreAuths auths ctxs v, HasServer api ctxs, AddSetCookies n (ServerT api Handler) (ServerT (AddSetCookiesApi n api) Handler), ToJWT v, HasContextEntry ctxs CookieSettings, HasContextEntry ctxs JWTSettings) => HasServer (Auth auths v :> api :: Type) ctxs

Why does v (in Auth auths v) need to be ToJWT?

From skimming the code it looks like the HasServer instance will set some cookies with the newly encoded JWT. How can I work around this and/or recover the original JWT (ideally without adding an IORef to the Context)?

@nmattia
Copy link

nmattia commented Mar 26, 2019

Related: #40 #119

@freckletonj
Copy link

Relatedly, I validate another service's JWTs, and I do so by polling for their signing key because it occasionally changes.

Is there a way to define Context '[TVar JWTSettings] so that I can validate JWTs based on an updateable key?

Or another way of addressing this, short of restarting the WAI app every time the key changes?

@nmattia
Copy link

nmattia commented Jun 17, 2019

Had the same use case, this might help: https://github.com/deckgo/deckdeckgo/blob/4c61f0f366e15cf5097bb947294d519473d92b85/infra/firebase-login/src/Servant/Auth/Firebase.hs#L53

@freckletonj
Copy link

freckletonj commented Jun 19, 2019

@nmattia Thanks! I've been trying to get this to work for the past day, and, whew. Servant. Tough Types.

In the end, I just used endpoints authed by Auth auths User

An uninhabited type like data TVarJWT

A fn like tVarJwtAuthCheck :: FromJWT usr => TVar JWTSettings -> AuthCheck usr

and an IsAuth instance like:

instance FromJWT usr => IsAuth TVarJWT usr where
  type AuthArgs TVarJWT = '[TVar JWTSettings]
  runAuth _ _ = tVarJwtAuthCheck

The one catch with doing things this way Servant's instance for HasServer ... Auth auths a :> sub requires HasContextEntry for CookieSettings and JWTSettings, meaning, even if you don't want them, you have to have them if you want to use the whole Auth auths User pattern.

I started writing a more general HasServer instance, but got super swamped. So. Hopefully this helps a future person mired in types.

@alpmestan
Copy link
Contributor

Yes, this is all a bit too strict to our taste, and there's a "roadmap" to address this: https://summer.haskell.org/ideas.html#servant-auth-improvements

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

No branches or pull requests

6 participants