Meteor OAuth2 Server
This package is a implementation of the package
@node-oauth/oauth2-server
for Meteor.
It can run without express
(we use Meteor's builtin WebApp
) and implements
the authorization_code
workflow and works like the Facebook's OAuth popup.
Changelog
View the full changelog in the history page.
Install
This package is a full scale drop-in, so you just need to add it via
$ meteor add leaonline:oauth2-server
Implementation
The package comes with a default config, so you can start immediately. However, we made it all configurable for you.
You can change various flags, routes and expiration times and collection names. The following sections will show you how to setup the server with a full configuration.
Server implementation
The following example uses the full configuration. The used values represent the 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, which has been generated 33// by the authorization_code grant flow 34// You will have to implement it to allow your remote apps 35// to retrieve the user credentials after successful 36// authentication. 37oauth2server.authenticatedRoute().get('/oauth/ident', function (req, res, next) { 38 const user = Meteor.users.findOne(req.data.user.id) 39 40 res.writeHead(200, { 41 'Content-Type': 'application/json', 42 }) 43 const body = JSON.stringify({ 44 id: user._id, 45 login: user.username, 46 email: user.emails[0].address, 47 firstName: user.firstName, 48 lastName: user.lastName, 49 name: `${user.firstName} ${user.lastName}` 50 }) 51 res.end(body) 52}) 53 54// create some fallback for all undefined routes 55oauth2server.app.use('*', function (req, res, next) { 56 res.writeHead(404) 57 res.end('route not found') 58})
Additional validation
Often, you want to restrict who of your users can access which client / service. In order to decide to give permission or not, you can register a handler that receives the authenticated user and the client she aims to access:
1oauth2server.validateUser(function({ user, client }) { 2 // the following example uses alanning:roles to check, whether a user 3 // has been assigned a role that indicates she can access the client. 4 // It is up to you how you implement this logic. If all users can access 5 // all registered clients, you can simply omit this call at all. 6 const { clientId } = client 7 const { _id } = user 8 9 return Roles.userIsInRoles(_id, 'manage-app', clientId) 10})
Client/Popup implementation
You should install a router to handle client side routing independently 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 (we use Blaze in this example but it will work with any of your preferred and loved frontends):
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}
API and Documentation
We also have an API documentation with further info on the package internals.
Furthermore we suggest you to consult the RFC docs on OAuth2:
- RFC 6749 - The OAuth 2.0 Authorization Framework
- RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
Testing
We use mocha with meteortesting:mocha
to run the tests.
We have now a full scale test project inside this one and you can use it
extensively to lint and test this project.
Setup
The setup is already prepared, so you just need to run a few commands:
$ cd test-proxy $ meteor npm install # install npm dependencies $ meteor npm run setup # link with package
Run the linter
After the setup from the previous section you can run the linter via
$ meteor npm run lint
or auto-fix code via
$ meteor npm run lint:fix
Note, that we use standardx
, which is standard
code style with a few extra
tweaks. We also use eslint-plugin-security
, which can sometimes create lots
of false-positives. If you need assistance, feel free to create an issue.
Run the tests
After the setup from the previous section you can run the tests via
$ meteor npm run test
or in watch mode via
$ meteor npm run test:watch
or with coverage report (+ watch mode) via
$ meteor npm run test:coverage
Build the docs
We use jsDoc and jsdoc2md to create a markdown file. To build the docs use
$ meteor npm run build:docs
License
MIT, see license file