leaonline:jwt

v0.0.1Published 4 months ago

Leaonline JWT

A simple JWT implementation that you can add to your Meteor applications in order to share data without the need of a shared database or shared authentication system (such as OAuth providers).

It currently uses node jwt (njwt) but can be easily replaced with any other JWT library.

Usage

First install the package on both applications via

$ meteor add leaonline:jwt

Make sure you understand consumer and provider applications. The consumer is the application that requests data from the provider application. The provider is the application that sends data to the consumer application. Consumer has to be able to connect to the provider application via DDP.

In theory, both apps can be consumer and provider at the same time, so you can share data in both directions.

Create JWTs on the consumer side

On the consumer side you want to create a JWT factory. Before you can do that, you need to add your jwt credentials to the Meteor settings (settings.json) of applications:

1{
2  "jwt": {
3    "key": "your-secret-key",
4    "sub": "a-subject-or-username-or-id",
5    "expires": 30000
6  }
7}

Now you can create a token factory:

1import { Meteor } from 'meteor/meteor'
2import { createJWTFactory } from 'meteor/leaonline:jwt'
3
4const { jwt } = Meteor.settings
5
6const tokenFactory = createJWTFactory({
7  url: Meteor.absoluteUrl(), // you can also define a custom URL here
8  key: jwt.key,
9  sub: jwt.sub,
10  expires: jw.expires, // optional, defaults to 60000 ms
11  debug: console.debug // optional
12})

Now you can generate new JWTS for different methods or publications (or http) endpoints. Let's say you want to consume a method, named getUserData from the provider app, then you can create a JWT like this:

1const token = tokenFactory({ name: 'getUserData' })
2const remote = DDP.connect('https://provider-app.com')
3
4// ... on successfully connected
5
6remote.call('getUserData', { token }, (error, result) => {
7  // ... process response
8})

Validate JWTs on the provider side

On the provider side you want to create a JWT validator so you can validate, if the consumer is allowed to call your methods or publications.

First, you need to add the same jwt credentials to the Meteor settings (settings.json) and define allowed / blocked consumers, using a positive list and/or negative list:

1{
2  "jwt": {
3    "key": "your-secret-key",
4    "hosts": [
5      {
6        "url": "https://consumer-app.com",
7        "sub": "a-subject-or-username-or-id"
8      }
9    ]
10  }
11}
1import { Meteor } from 'meteor/meteor'
2import { createJWTValidator } from 'meteor/leaonline:jwt'
3
4const { jwt } = Meteor.settings
5const validator = createJWTValidator({
6  key: jwt.key,
7  positives: jwt.hosts, // optional, defaults to [],
8  negatives: [], // optional, defaults to []
9  debug: console.debug // optional
10})
11
12Meteor.methods({
13  getUserData({ token }) {
14    const { valid, reason } = validator(token, 'getUserData')
15
16    if (!valid) {
17      throw new Meteor.Error('unauthorized', `JWT validation failed: ${reason}`)
18    }
19    
20    // ... process the request, e.g. return user data
21    return { userId: '12345', name: 'John Doe' }
22  }
23})

Using a Mixin (for ValidatdMethod)

You can also use a mixin for validated methods that automatically validates the JWT and removes it when passing:

1import { Meteor } from 'meteor/meteor'
2import { createJWTValidator } from 'meteor/leaonline:jwt'
3
4const { jwt } = Meteor.settings
5const validator = createJWTValidator({
6  key: jwt.key,
7  positives: jwt.hosts, // optional, defaults to [],
8  negatives: [], // optional, defaults to []
9  debug: console.debug // optional
10})
11
12export const validteJWTMixin = options => {
13  const { name, validate } = options
14  
15  options.validate = args => {
16    const { token, ...data } = args
17    const { valid, reason } = validator(token, name)
18    
19    if (!valid) {
20      throw new Meteor.Error('unauthorized', `JWT validation failed: ${reason}`)
21    }
22    
23    // if we remove token then we don't need
24    // to consider it in the method validations
25    delete data.token
26    
27    // run the original validation
28    validate(data)
29  }
30  
31  return options
32}

Contributing / development

Make sure to run lint, format and test before committing your changes. Please use conventional commit messages, so we can generate a changelog automatically.

License

MIT, see LICENSE