Developers
Extensions
Login Providers

Login providers

Madoc uses PassportJS to handle authentication. PassportJS is a middleware that allows you to use different authentication providers. Madoc does not support registration using PassportJS, instead allowing users to link accounts to make it easier to login. This is something we could add in the future.

The only provider that is currently supported is GitHub. This was done to validate the concept of linking accounts.

Adding a new provider

GitHub
Auth providers can be found in the codebase at
services/madoc-ts/src/auth

The example provider for GitHub is fairly simple.

import passport from 'koa-passport';
import { Strategy as GitHubStrategy } from 'passport-github2';
import { RouteMiddleware } from '../types/route-middleware';
import { TypedRouter } from '../utility/typed-router';
 
const clientID = process.env.GITHUB_CLIENT_ID || '';
const clientSecret = process.env.GITHUB_CLIENT_SECRET || '';
const callbackURL = process.env.GITHUB_CLIENT_CALLBACK_URL || '';
 
export const github = {
  isAvailable() {
    return clientID && clientSecret && callbackURL;
  },
  register() {
    if (!github.isAvailable()) {
      throw new Error('Github not enabled');
    }
    passport.use(
      new GitHubStrategy(
        {
          clientID,
          clientSecret,
          callbackURL,
        },
        (accessToken: string, refreshToken: string, profile: any, done: any) => {
          fetch(`https://api.github.com/user/emails`, {
            headers: {
              Accept: 'application/vnd.github.v3+json',
              Authorization: `token ${accessToken}`,
            },
          })
            .then(resp => resp.json())
            .then(emails => {
              const primary = emails.find((e: any) => e.verified && e.primary);
 
              if (!primary) {
                done(new Error('No valid email'));
                return;
              }
 
              done(null, {
                id: profile.id,
                provider: 'github',
                details: {
                  name: profile.displayName,
                  username: profile.username,
                  email: primary.email,
                },
              });
            });
        }
      )
    );
  },
  router(loginWithProvider: RouteMiddleware) {
    return {
      'auth/github': [TypedRouter.GET, '/auth/github', [passport.authenticate('github', { scope: ['user:email'] })]],
      'auth/github-callback': [
        TypedRouter.GET,
        '/auth/github/callback',
        [passport.authenticate('github', { failureRedirect: '/login', session: false }), loginWithProvider],
      ],
    };
  },
};

There are 3 elements that need to be implemented for a new provider:

  • isAvailable() - This is used to check if the provider is available. This is used to check if the provider is configured correctly from environment variables.
  • register() - This is used to register the provider with PassportJS. This is where you would add the strategy for the provider, usually through a PassportJS plugin.
  • router() - This is used to register the routes for the provider. This is where you would add the routes for the provider.

When implementing the register() you will need to return a promise that contains the following structure:

const resp = {
  id: 'abc-123', // A unique ID for the user for this provider.
  provider: 'github', // The name of the provider.
  details: {
    name: 'User Name', // The name of the user.
    username: 'user-name', // The username of the user for this provider.
    email: '', // The email that will match the user in Madoc.
  },
}

Issues

This extension point has not been fully utilised, and would require some work to get things going. There is not currently a way to expand the frontend to show login buttons for providers. There is also no way to link accounts together for the end users. This is a UX issue that would need to be resolved.

There is also the issue that the email that is returned from the provider may not match the email that is used in Madoc. We could get around this by allowing multiple emails to be linked to a single account, but this would require some work to get working correctly.

However, the functional piece of authenticating and storing the credentials is working correctly.