leaonline:oauth2-server

v3.1.0Published 3 years ago

Project Status: Active – The project has reached a stable, usable state and is being actively developed. JavaScript Style Guide

oauth2-server

This package is a implementation of the package node-oauth2-server (v3) for Meteor. It runs without express and implements the authorization_code workflow and works like the Facebook's OAuth popup.

Changelog

View the full changelog in the hsitory page.

Install

meteor add leaonline:oauth2-server

Implementation

Server implementation

The following example uses the full configuration with their current default values.

server/oauth2server.js

1import { Meteor } from "meteor/meteor"
2import { OAuth2Server } from 'meteor/leaonline:oauth2-server'
3
4const oauth2server = new OAuth2Server({
5  serverOptions: {
6    addAcceptedScopesHeader: true,
7    addAuthorizedScopesHeader: true,
8    allowBearerTokensInQueryString: false,
9    allowEmptyState: false,
10    authorizationCodeLifetime: 300,
11    accessTokenLifetime: 3600,
12    refreshTokenLifetime: 1209600,
13    allowExtendedTokenAttributes: false,
14    requireClientAuthentication: true
15  },
16  model: {
17    accessTokensCollectionName: 'oauth_access_tokens',
18    refreshTokensCollectionName: 'oauth_refresh_tokens',
19    clientsCollectionName: 'oauth_clients',
20    authCodesCollectionName: 'oauth_auth_codes',
21    debug: true
22  },
23  routes: {
24    accessTokenUrl: '/oauth/token',
25    authorizeUrl: '/oauth/authorize',
26    errorUrl: '/oauth/error',
27    fallbackUrl: '/oauth/*'
28  }
29})
30
31// this is a "secret" route that is only accessed with
32// a valid token, that has been generated by the authorization_code grant flow
33oauth2server.authenticatedRoute().get('/oauth/ident', function (req, res, next) {
34  const user = Meteor.users.findOne(req.data.user.id)
35
36  res.writeHead(200, {
37    'Content-Type': 'application/json',
38  })
39  const body = JSON.stringify({
40    id: user._id,
41    login: user.username,
42    email: user.emails[0].address,
43    firstName: user.firstName,
44    lastName: user.lastName,
45    name: `${user.firstName} ${user.lastName}`
46  })
47  res.end(body)
48})
49
50oauth2server.app.use('*', function (req, res, next) {
51  res.writeHead(404)
52  res.end('route not found')
53})
54

Client/Popup implementation

You should install a router to handle client side routing indendently from the WebApp routes. You can for example use

$ meteor add ostrio:flow-router-extra

and then define a client route for your popup dialog:

client/main.html

1<head>
2    <title>authserver</title>
3</head>
4
5<template name="layout">
6    {{> yield}}
7</template>

client/main.js

1import { FlowRouter } from 'meteor/ostrio:flow-router-extra'
2import './authorize.html'
3import './authorize'
4import './main.html'
5
6// Define the route to render the popup view
7FlowRouter.route('/dialog/oauth', {
8  action: function (params, queryParams) {
9    this.render('layout', 'authorize', queryParams)
10  }
11})

client/authorize.js

1// Subscribe the list of already authorized clients
2// to auto accept
3Template.authorize.onCreated(function() {
4  this.subscribe('authorizedOAuth');
5});
6
7// Get the login token to pass to oauth
8// This is the best way to identify the logged user
9Template.authorize.helpers({
10  getToken: function() {
11    return localStorage.getItem('Meteor.loginToken');
12  }
13});
14
15// Auto click the submit/accept button if user already
16// accepted this client
17Template.authorize.onRendered(function() {
18  var data = this.data;
19  this.autorun(function(c) {
20    var user = Meteor.user();
21    if (user && user.oauth && user.oauth.authorizedClients && user.oauth.authorizedClients.indexOf(data.client_id()) > -1) {
22      c.stop();
23      $('button').click();
24    }
25  });
26});

client/authorize.html

1<template name="authorize">
2  {{#if currentUser}}
3    <form method="post" action="{{redirect_uri}}" role="form" class="{{#unless Template.subscriptionsReady}}hidden{{/unless}}">
4      <h2>Authorise</h2>
5      <input type="hidden" name="allow" value="yes">
6      <input type="hidden" name="token" value="{{getToken}}">
7      <input type="hidden" name="client_id" value="{{client_id}}">
8      <input type="hidden" name="redirect_uri" value="{{redirect_uri}}">
9      <input type="hidden" name="response_type" value="code">
10      <button type="submit">Authorise</button>
11    </form>
12    {{#unless Template.subscriptionsReady}}
13      loading...
14    {{/unless}}
15  {{else}}
16    {{> loginButtons}}
17  {{/if}}
18</template>

client/style.css

1.hidden {
2  display: none;
3}

Testing

We use mocha with meteortesting:mocha to run the tests. You can run the tests in watch mode via

TEST_WATCH=1 TEST_CLIENT=0 meteor test-packages ./ --driver-package meteortesting:mocha

License

MIT, see license file