Code Monkey home page Code Monkey logo

oauth2-server's Introduction

Meteor OAuth2 Server

Test suite CodeQL built with Meteor JavaScript Style Guide Project Status: Active – The project has reached a stable, usable state and is being actively developed. GitHub DOI

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

import { Meteor } from "meteor/meteor"
import { OAuth2Server } from 'meteor/leaonline:oauth2-server'

const oauth2server = new OAuth2Server({
  serverOptions: {
    addAcceptedScopesHeader: true,
    addAuthorizedScopesHeader: true,
    allowBearerTokensInQueryString: false,
    allowEmptyState: false,
    authorizationCodeLifetime: 300,
    accessTokenLifetime: 3600,
    refreshTokenLifetime: 1209600,
    allowExtendedTokenAttributes: false,
    requireClientAuthentication: true
  },
  model: {
    accessTokensCollectionName: 'oauth_access_tokens',
    refreshTokensCollectionName: 'oauth_refresh_tokens',
    clientsCollectionName: 'oauth_clients',
    authCodesCollectionName: 'oauth_auth_codes',
    debug: true
  },
  routes: {
    accessTokenUrl: '/oauth/token',
    authorizeUrl: '/oauth/authorize',
    errorUrl: '/oauth/error',
    fallbackUrl: '/oauth/*'
  }
})

// this is a "secret" route that is only accessed with
// a valid token, which has been generated 
// by the authorization_code grant flow
// You will have to implement it to allow your remote apps
// to retrieve the user credentials after successful
// authentication.
oauth2server.authenticatedRoute().get('/oauth/ident', function (req, res, next) {
  const user = Meteor.users.findOne(req.data.user.id)

  res.writeHead(200, {
    'Content-Type': 'application/json',
  })
  const body = JSON.stringify({
    id: user._id,
    login: user.username,
    email: user.emails[0].address,
    firstName: user.firstName,
    lastName: user.lastName,
    name: `${user.firstName} ${user.lastName}`
  })
  res.end(body)
})

// create some fallback for all undefined routes
oauth2server.app.use('*', function (req, res, next) {
  res.writeHead(404)
  res.end('route not found')
})

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:

oauth2server.validateUser(function({ user, client }) {
  // the following example uses alanning:roles to check, whether a user
  // has been assigned a role that indicates she can access the client.
  // It is up to you how you implement this logic. If all users can access
  // all registered clients, you can simply omit this call at all.
  const { clientId } = client
  const { _id } = user
  
  return Roles.userIsInRoles(_id, 'manage-app', clientId)
})

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

<head>
    <title>authserver</title>
</head>

<template name="layout">
    {{> yield}}
</template>

client/main.js

import { FlowRouter } from 'meteor/ostrio:flow-router-extra'
import './authorize.html'
import './authorize'
import './main.html'

// Define the route to render the popup view
FlowRouter.route('/dialog/oauth', {
  action: function (params, queryParams) {
    this.render('layout', 'authorize', queryParams)
  }
})

client/authorize.js

// Subscribe the list of already authorized clients
// to auto accept
Template.authorize.onCreated(function() {
  this.subscribe('authorizedOAuth');
});

// Get the login token to pass to oauth
// This is the best way to identify the logged user
Template.authorize.helpers({
  getToken: function() {
    return localStorage.getItem('Meteor.loginToken');
  }
});

// Auto click the submit/accept button if user already
// accepted this client
Template.authorize.onRendered(function() {
  var data = this.data;
  this.autorun(function(c) {
    var user = Meteor.user();
    if (user && user.oauth && user.oauth.authorizedClients && user.oauth.authorizedClients.indexOf(data.client_id()) > -1) {
      c.stop();
      $('button').click();
    }
  });
});

client/authorize.html

<template name="authorize">
  {{#if currentUser}}
    <form method="post" action="{{redirect_uri}}" role="form" class="{{#unless Template.subscriptionsReady}}hidden{{/unless}}">
      <h2>Authorise</h2>
      <input type="hidden" name="allow" value="yes">
      <input type="hidden" name="token" value="{{getToken}}">
      <input type="hidden" name="client_id" value="{{client_id}}">
      <input type="hidden" name="redirect_uri" value="{{redirect_uri}}">
      <input type="hidden" name="response_type" value="code">
      <button type="submit">Authorise</button>
    </form>
    {{#unless Template.subscriptionsReady}}
      loading...
    {{/unless}}
  {{else}}
    {{> loginButtons}}
  {{/if}}
</template>

client/style.css

.hidden {
  display: none;
}

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:

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

oauth2-server's People

Contributors

engelgabriel avatar graywolf336 avatar jankapunkt avatar maliyildiz1 avatar pagebakers avatar pierre-lehnen-rc avatar rodrigok avatar sampaiodiego avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

oauth2-server's Issues

update ci

  • update actions to v3
  • update node to 14
  • update Meteor to 2.8.1

Error in callback when unregistered client requests the authorization GET route

I20190830-14:03:45.614(2)? Exception in callback of async function: Error: Can't set headers after they are sent.
I20190830-14:03:45.614(2)?     at validateHeader (_http_outgoing.js:491:11)
I20190830-14:03:45.614(2)?     at ServerResponse.setHeader (_http_outgoing.js:498:3)
I20190830-14:03:45.614(2)?     at ServerResponse.setWriteHeadHeaders (/Users/admin/.meteor/packages/webapp/.1.7.4.11um7dv.19c5h++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/on-headers/index.js:82:19)
I20190830-14:03:45.614(2)?     at ServerResponse.writeHead (/Users/admin/.meteor/packages/webapp/.1.7.4.11um7dv.19c5h++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/on-headers/index.js:41:36)
I20190830-14:03:45.614(2)?     at errorHandler (packages/leaonline:oauth2-server/error.js:3:7)
I20190830-14:03:45.614(2)?     at packages/leaonline:oauth2-server/oauth.js:155:11
I20190830-14:03:45.615(2)?     at runWithEnvironment (packages/meteor.js:1286:24)
I20190830-14:03:45.615(2)?     at packages/meteor.js:1299:14
I20190830-14:03:45.615(2)?     at packages/leaonline:oauth2-server/webapp.js:11:15
I20190830-14:03:45.615(2)?     at /Users/admin/.meteor/packages/promise/.0.11.2.hy69oz.ytuae++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:43:40
W20190830-14:03:45.615(2)? (STDERR) [ERROR] 500 - unauthorized_client - This client is not authorized to use this service

refresh_token request to /oauth/token not possible

Describe the bug
According to RFC 6749, a refresh of an access token only requires the grant_type and the refresh_token. However, an incoming request fails with the message [validation error]: key <code> => expected <[object Object]>, got <undefined>.

To Reproduce
Steps to reproduce the behavior:

  1. Setup a oauth2 according to the docs, enable debug.
  2. request an access token (eg. via postman)
  3. place a refresh token request, with a body like {refresh_token: "12345xyz", grant_type: "refresh_token"}
  4. check the logs

Expected behavior
Refresh request should provide a fresh access token

Linter not running on macOX

Describe the bug
I do not get the linter to run on macOS.

To Reproduce
Steps to reproduce the behavior:

  1. Install test-proxy according to the contribution guidelines.
  2. Run meteor npm run lint

Expected behavior
Lint script should run without errors.

Logs


> test-proxy@ lint /folder/oauth2-server/test-proxy
> standardx -v ./packages/oauth2-server | snazzy

internal/modules/cjs/loader.js:936
  throw err;
  ^

Error: Cannot find module './lib/_stream_readable.js'
Require stack:
- /folder/oauth2-server/test-proxy/node_modules/readable-stream/readable.js
- /folder/oauth2-server/test-proxy/node_modules/snazzy/index.js
- /folder/oauth2-server/test-proxy/node_modules/snazzy/bin/cmd.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:933:15)
    at Function.Module._load (internal/modules/cjs/loader.js:776:27)
    at Module.require (internal/modules/cjs/loader.js:1005:19)
    at require (internal/modules/cjs/helpers.js:107:18)
    at Object.<anonymous> (/folder/oauth2-server/test-proxy/node_modules/readable-stream/readable.js:7:30)
    at Module._compile (internal/modules/cjs/loader.js:1116:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1145:10)
    at Module.load (internal/modules/cjs/loader.js:981:32)
    at Function.Module._load (internal/modules/cjs/loader.js:821:12)
    at Module.require (internal/modules/cjs/loader.js:1005:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/folder/oauth2-server/test-proxy/node_modules/readable-stream/readable.js',
    '/folder/oauth2-server/test-proxy/node_modules/snazzy/index.js',
    '/folder/oauth2-server/test-proxy/node_modules/snazzy/bin/cmd.js'
  ]
}
standardx: Unexpected linter output:

Error: Failed to load plugin 'react' declared in 'BaseConfig » eslint-config-standard-jsx': Cannot find module 'es-errors'
Require stack:
- /folder/oauth2-server/test-proxy/node_modules/get-intrinsic/index.js
- /folder/oauth2-server/test-proxy/node_modules/call-bind/index.js
- /folder/oauth2-server/test-proxy/node_modules/object.fromentries/index.js
- /folder/oauth2-server/test-proxy/node_modules/eslint-plugin-react/index.js
- /folder/oauth2-server/test-proxy/node_modules/@eslint/eslintrc/lib/config-array-factory.js
- /folder/oauth2-server/test-proxy/node_modules/@eslint/eslintrc/lib/index.js
- /folder/oauth2-server/test-proxy/node_modules/eslint/lib/cli-engine/cli-engine.js
- /folder/oauth2-server/test-proxy/node_modules/eslint/lib/cli-engine/index.js
- /folder/oauth2-server/test-proxy/node_modules/eslint/lib/api.js
- /folder/oauth2-server/test-proxy/node_modules/standard/options.js
- /folder/oauth2-server/test-proxy/node_modules/standardx/options.js
- /folder/oauth2-server/test-proxy/node_modules/standardx/bin/cmd.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:933:15)
    at Function.Module._load (internal/modules/cjs/loader.js:776:27)
    at Module.require (internal/modules/cjs/loader.js:1005:19)
    at require (internal/modules/cjs/helpers.js:107:18)
    at Object.<anonymous> (/folder/oauth2-server/test-proxy/node_modules/get-intrinsic/index.js:5:14)
    at Module._compile (internal/modules/cjs/loader.js:1116:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1145:10)
    at Module.load (internal/modules/cjs/loader.js:981:32)
    at Function.Module._load (internal/modules/cjs/loader.js:821:12)
    at Module.require (internal/modules/cjs/loader.js:1005:19)

If you think this is a bug in `standardx`, open an issue: https://github.com/standard/standardx/issues
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! test-proxy@ lint: `standardx -v ./packages/oauth2-server | snazzy`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the test-proxy@ lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

Desktop (please complete the following information):

  • OS: macOS Sonoma 14.3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.