http-cas-client

A complete implement of CAS Client middleware for Node.js, support CAS 1.0, 2.0+, 3.0+ protocol.

Usage no npm install needed!

<script type="module">
  import httpCasClient from 'https://cdn.skypack.dev/http-cas-client';
</script>

README

http-cas-client

Build Status NPM Downloads NPM Version

A complete implement of CAS Client middleware for Node.js, support CAS 1.0, 2.0+, 3.0+ protocol.

CAS(Central Authentication Service) is a single-sign-on / single-sign-off protocol for the web.

We suppose you are already familiar with the CAS protocol, if not, please read this document before you use this.

Installation

$ npm install http-cas-client

Feature

  • Singing Sign On (SSO)
  • CAS Proxy with axios api for POST, PUT, DELETE...
  • CAS Proxy - proxiex chain checking
  • Single Logout (SLO)
  • Renew & gateway
  • Native http module supported
  • No-session mode supported
  • Easy to wrap for all popular backend framework integration
  • Cluster friendly //TODO
  • Principal adapter for application debugging //TODO
  • SMAL1.1 //TODO
  • Restlet integration supported //TODO
  • Conditional skip CAS authentication //TODO
  • PT cache //TODO

Getting Started

CAS Client Handler for Native

Basic usage with default options for creating a cas client handler and use it in nodejs native http server. For example,

const http = require('http');
const httpCasClient = require('http-cas-client');

// A CAS server deployed at url http://localhost:8080.
// Default prefix is '/'.
const handler = httpCasClient({
    casServerUrlPrefix: 'http://localhost:9000/cas',
    serverName: 'http://127.0.0.1'
});

http.createServer(async (req, res) => {
    if(!await handler(req, res)) {
        return res.end();
    }

    const { principal, ticket } = req;
    console.log(principal, ticket);
    // { user: 'test', attributes: { ... } }

    // your statements...
    res.end('hello world');
}).listen(80);

Koa2 Wrap

It can also use with popular backend framework like Koa2. For example,

const koaCasClient = require('http-cas-client/wrap/koa2');
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');

const casServerUrlPrefix = 'http://localhost:9000/cas';

const app = new Koa();

// NOTICE: If you put bodyparser after cas client, bodyparser will not receive req.socket data.
// A native body parser is used in handler.
// Sometimes you need to make some adjustments for your especial case.
app.use(bodyparser());

// NOTICE: Put the middleware include casClientHandler before your specific api code.
// For example, put it before routes.
app.use(koaCasClient({
    casServerUrlPrefix: 'http://localhost:9000/cas',
    serverName: 'http://127.0.0.1'
})).use(ctx => {
    const { principal, ticket } = ctx;

    // your statements...
    ctx.body = `<a href="${casServerUrlPrefix}/logout">SLO</a><pre>`
    ctx.body += JSON.stringify(principal, null, 2);
    ctx.body += '</pre>'
}).listen(80);

Under the traditional pattern, cas client NEED to use with session, especially those 'Spring mvc' project in servlet.

So there is also a way to implement with all nodejs backend frameworks. For example in Koa2,

const koaSessionCasClient = require('http-cas-client/wrap/koa2-session');
const session = require('koa-session');
const Koa = require('koa');
const casServerUrlPrefix = 'http://localhost:9000/cas';

const app = new Koa();
const sessionMiddleware = session(app);
app.keys = ['koa-app'];

app.use(session(app)).use(koaSessionCasClient({
    casServerUrlPrefix: 'http://localhost:9000/cas',
    serverName: 'http://127.0.0.1'
})).use(ctx => {
    const { principal } = ctx;

    // your statements...
    ctx.body = `<a href="${casServerUrlPrefix}/logout">SLO</a><pre>`
    ctx.body += JSON.stringify(principal, null, 2);
    ctx.body += '</pre>'
}).listen(80);

CAS Proxy (Use ServiceTicket instance)

const koaSessionCasClient = require('http-cas-client/wrap/koa2-session');
const koaCasClient = require('http-cas-client/wrap/koa2');
const Koa = require('koa');
const session = require('koa-session');

const casServerUrlPrefix = 'http://localhost:9000/cas';

const app = new Koa();
app.keys = ['koa-app'];
app.use(session(app)).use(koaSessionCasClient({
    casServerUrlPrefix: 'http://localhost:9000/cas',
    serverName: 'http://127.0.0.1:2000',
    client: {
        proxy: {
            callbackUrl: 'http://127.0.0.1:2000/callback',
            receptorUrl: '/callback'
        }
    }
})).use((ctx, next) => {
    // If req.url matches /app, skip into next middleware.
    if (ctx.request.path === '/app') {
        return next();
    }

    const { principal } = ctx;

    // This is the 1st cas client.
    // your statements...
    ctx.body = `<p>This is proxy</p>`;
    ctx.body = `<a href="${casServerUrlPrefix}/logout">SLO</a><pre>`
    ctx.body += JSON.stringify(principal, null, 2);
    ctx.body += '</pre>'
}).use(async ctx => {
    const { ticket } = ctx;

    // Proxy request some data from 2rd cas client 'http://127.0.0.1:3000'.
    const response = await ticket.request('http://127.0.0.1:3000/').then(axios => {
        return axios.post('/test');
    });

    // Proxy send the data from 2rd.
    ctx.body = response.data;
}).listen(2000);

const app2 = new Koa();
app2.use(koaCasClient({
    casServerUrlPrefix: 'http://localhost:9000/cas',
    serverName: 'http://127.0.0.1:3000',
    client: {
        proxy: {
            acceptAny: true,
            callbackUrl: 'http://127.0.0.1:3000/proxyCallback',
            receptorUrl: '/proxyCallback'
        }
    }
})).use(async ctx => {
    //This is the 2nd cas client.
    const { principal, ticket } = ctx;

    // Proxy request some data from 3rd cas client 'http://127.0.0.1:4000/'.
    const response = await ticket.request('http://127.0.0.1:4000/').then(axios => {
        return axios.post('/test');
    });

    // your statements...
    ctx.body = `<p>This is App</p>`;
    ctx.body += `<a href="${casServerUrlPrefix}/logout">SLO</a><pre>`;
    ctx.body += JSON.stringify(principal, null, 2);
    ctx.body += '</pre>';

    // Combine data from the 3rd.
    ctx.body += response.data;
}).listen(3000);

const app3 = new Koa();
app3.use(koaCasClient({
    casServerUrlPrefix: 'http://localhost:9000/cas',
    serverName: 'http://127.0.0.1:4000',
    client: {
        proxy: {
            acceptAny: true,
        }
    }
})).use(async ctx => {
    // This is the 3rd cas client.
    // your statements...
    ctx.body = 'from the proxy proxy app.'
}).listen(4000);

Custom Wrap

//TODO But you can review 'http-cas-client/wrap/koa2.js'. Maybe you can understand how to do.

At least I think that's easy. Good luck.

API Referrence

Options

The origin of CAS Server is essential. The simplest form is like,

const httpCasClient = require('http-cas-client');
const handler = httpCasClient({
    casServerUrlPrefix: 'http://localhost:9000/cas',
    serverName: 'http://127.0.0.1',
});

Full Default Options

// Other items are optional.
const defaultOptions = {
    cas: 3, // CAS protocol version 1, 2, 3

    client: {
        service: null,
        slo: true, // Use SLO?
        renew: false, // CAS renew.
        gateway: false, // CAS gateway
        useSession: false,
        method: 'GET',
        // The resource path rules let cas client ignore.
        ignore: [/\.(ico|css|js|jpe?g|svg|png)/],
        proxy: {
            acceptAny: false,
            allowedChains: () => true,
            callbackUrl: null,
            receptorUrl: null
        }
    },

    server: {
        loginUrl: null,

        // CAS Server URIs. Normally no change is required.
        // Useful when use a nonstandard cas server or url re-writed.
        path: {
            login: '/login',
            logout: '/logout',
            validate: '/validate',
            serviceValidate: '/serviceValidate',
            proxyValidate: '/proxyValidate',
            proxy: '/proxy',
            p3: {
                serviceValidate: '/p3/serviceValidate',
                proxyValidate: '/p3/proxyValidate',
            }
        }
    }
}

Why these paths? See also, CAS Protocol 3.0 Specification.

Customs Options for Special Purposes

You can also override all items of options for your special cas server. And use chained multiple options arguments to create handler,

const casClientHandler = httpCasClient(options1, options2, ...);

Store

Store is an event emitter.

Method

//TODO

Event

//TODO

CAS Client Cluster

Shared Ticket Store

Sync ST store

In Frontend

In Backend

Debug

Because CAS protocol is complicated, we remove this option. We recommend you to always log every step that what CAS client do on your production environment.

Setting env.DEBUG="cas,cas:*";

License

MIT