Skip to content

Commit aad9929

Browse files
committed
example: provide separate examples for express, koa, standalone
1 parent d54c313 commit aad9929

File tree

15 files changed

+475
-205
lines changed

15 files changed

+475
-205
lines changed

README.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,15 @@ of the OpenID Connect™ protocol.
7676

7777

7878
## Get started
79-
You may follow an example [step by step setup][example-repo] (recommended), or run and experiment with an
80-
example server that's part of the repo (if you can follow the structure, if not check the step by step).
79+
You may check the [example folder](/example) or follow a [step by step example][example-repo] to see
80+
which of those fits your desired application setup.
8181

82-
The example bundled in this repo's codebase is available for you to experiment with [here][heroku-example].
83-
Dynamic Registration is open, you can literally register any
84-
client you want there.
85-
An example client using this provider is available [here][heroku-example-client]
86-
(uses [openid-client][openid-client]).
82+
The examples bundled in this repo's codebase are available for you to experiment with
83+
[here][heroku-example]. Dynamic Registration is open, you can literally register any client you want
84+
there. An example client using this provider is available [here][heroku-example-client] (uses
85+
[openid-client][openid-client]).
86+
87+
Also be sure to check the available configuration docs section.
8788

8889

8990
## Configuration and Initialization
@@ -104,13 +105,21 @@ const clients = [{
104105

105106
const oidc = new Provider('http://localhost:3000', configuration);
106107

108+
let server;
107109
(async () => {
108110
await oidc.initialize({ clients });
109-
// oidc.callback => express/nodejs style application callback (req, res)
110-
// oidc.app => koa2.x application
111-
oidc.listen(3000);
112-
console.log('oidc-provider listening on port 3000, check http://localhost:3000/.well-known/openid-configuration');
111+
// express/nodejs style application callback (req, res, next) for use with express apps, see ./examples/express.js
112+
oidc.callback
113+
114+
// koa application for use with koa apps, see ./examples/koa.js
115+
oidc.app
116+
117+
// or just expose a server standalone, see ./examples/standalone.js
118+
server = oidc.listen(3000, () => {
119+
console.log('oidc-provider listening on port 3000, check http://localhost:3000/.well-known/openid-configuration');
120+
});
113121
})().catch((err) => {
122+
if (server && server.listening) server.close();
114123
console.error(err);
115124
process.exitCode = 1;
116125
});

example/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Examples
2+
3+
See the following examples
4+
5+
- [Koa](/example/koa.js)
6+
- the provider is a part of your Koa application
7+
- [Express](/example/express.js)
8+
- the provider is a part of your express application
9+
- [Standalone](/example/standalone.js)
10+
- the provider is completely standalone
11+
12+
Further resources
13+
14+
- [Configuration](/docs/configuration.md)
15+
- [Mounting to a path](/docs/configuration.md#mounting-oidc-provider)
16+
17+
Useful to know oidc-provider dependencies
18+
- [Koa](https://koajs.com/) - web framework oidc-provider uses internally
19+
- [node-jose](https://github.com/cisco/node-jose) - everything JOSE

example/express.js

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,72 @@
1-
// You should read the Koa corresponding example (index.js in this directory) to
2-
// learn about many possible options.
1+
/* eslint-disable no-console */
32

4-
const express = require('express'); // eslint-disable-line import/no-unresolved
3+
const path = require('path');
4+
const url = require('url');
55

6-
const Provider = require('../lib');
6+
const { set } = require('lodash');
7+
const express = require('express');
8+
const helmet = require('helmet');
9+
10+
const Provider = require('../lib'); // require('oidc-provider');
11+
12+
const Account = require('./support/account');
13+
const { provider: providerConfiguration, clients, keys } = require('./support/configuration');
14+
const { express: routes } = require('./routes');
15+
16+
const { PORT = 3000, ISSUER = `http://localhost:${PORT}`, TIMEOUT } = process.env;
17+
providerConfiguration.findById = Account.findById;
718

819
const app = express();
20+
app.use(helmet());
921

10-
const provider = new Provider('http://localhost:3000/op', {
11-
formats: { default: 'opaque' },
12-
});
22+
app.set('views', path.join(__dirname, 'views'));
23+
app.set('view engine', 'ejs');
24+
25+
const provider = new Provider(ISSUER, providerConfiguration);
26+
27+
if (TIMEOUT) {
28+
provider.defaultHttpOptions = { timeout: TIMEOUT };
29+
}
30+
31+
let server;
32+
(async () => {
33+
await provider.initialize({
34+
adapter: process.env.MONGODB_URI ? require('./support/heroku_mongo_adapter') : undefined, // eslint-disable-line global-require
35+
clients,
36+
keystore: { keys },
37+
});
38+
39+
if (process.env.NODE_ENV === 'production') {
40+
app.enable('trust proxy');
41+
provider.proxy = true;
42+
set(providerConfiguration, 'cookies.short.secure', true);
43+
set(providerConfiguration, 'cookies.long.secure', true);
44+
45+
app.use((req, res, next) => {
46+
if (req.secure) {
47+
next();
48+
} else if (req.method === 'GET' || req.method === 'HEAD') {
49+
res.redirect(url.format({
50+
protocol: 'https',
51+
host: req.get('host'),
52+
pathname: req.originalUrl,
53+
}));
54+
} else {
55+
res.status(400).json({
56+
error: 'invalid_request',
57+
error_description: 'do yourself a favor and only use https',
58+
});
59+
}
60+
});
61+
}
1362

14-
provider.initialize().then(() => {
15-
app.use('/op', provider.callback);
16-
app.listen(3000);
63+
routes(app, provider);
64+
app.use(provider.callback);
65+
server = app.listen(PORT, () => {
66+
console.log(`application is listening on port ${PORT}, check it's /.well-known/openid-configuration`);
67+
});
68+
})().catch((err) => {
69+
if (server && server.listening) server.close();
70+
console.error(err);
71+
process.exitCode = 1;
1772
});

example/koa.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* eslint-disable no-console */
2+
3+
const path = require('path');
4+
5+
const { set } = require('lodash');
6+
const Koa = require('koa');
7+
const render = require('koa-ejs');
8+
const helmet = require('koa-helmet');
9+
const mount = require('koa-mount');
10+
11+
const Provider = require('../lib'); // require('oidc-provider');
12+
13+
const Account = require('./support/account');
14+
const { provider: providerConfiguration, clients, keys } = require('./support/configuration');
15+
const { koa: routes } = require('./routes');
16+
17+
const { PORT = 3000, ISSUER = `http://localhost:${PORT}`, TIMEOUT } = process.env;
18+
providerConfiguration.findById = Account.findById;
19+
20+
const app = new Koa();
21+
app.use(helmet());
22+
render(app, {
23+
cache: false,
24+
viewExt: 'ejs',
25+
layout: '_layout',
26+
root: path.join(__dirname, 'views'),
27+
});
28+
29+
if (process.env.NODE_ENV === 'production') {
30+
app.keys = providerConfiguration.cookies.keys;
31+
app.proxy = true;
32+
set(providerConfiguration, 'cookies.short.secure', true);
33+
set(providerConfiguration, 'cookies.long.secure', true);
34+
35+
app.use(async (ctx, next) => {
36+
if (ctx.secure) {
37+
await next();
38+
} else if (ctx.method === 'GET' || ctx.method === 'HEAD') {
39+
ctx.redirect(ctx.href.replace(/^http:\/\//i, 'https://'));
40+
} else {
41+
ctx.body = {
42+
error: 'invalid_request',
43+
error_description: 'do yourself a favor and only use https',
44+
};
45+
ctx.status = 400;
46+
}
47+
});
48+
}
49+
50+
const provider = new Provider(ISSUER, providerConfiguration);
51+
if (TIMEOUT) {
52+
provider.defaultHttpOptions = { timeout: TIMEOUT };
53+
}
54+
55+
let server;
56+
(async () => {
57+
await provider.initialize({
58+
adapter: process.env.MONGODB_URI ? require('./support/heroku_mongo_adapter') : undefined, // eslint-disable-line global-require
59+
clients,
60+
keystore: { keys },
61+
});
62+
app.use(routes(provider).routes());
63+
app.use(mount(provider.app));
64+
server = app.listen(PORT, () => {
65+
console.log(`application is listening on port ${PORT}, check it's /.well-known/openid-configuration`);
66+
});
67+
})().catch((err) => {
68+
if (server && server.listening) server.close();
69+
console.error(err);
70+
process.exitCode = 1;
71+
});

example/routes/express.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
const querystring = require('querystring');
2+
3+
const { urlencoded } = require('express');
4+
5+
const Account = require('../support/account');
6+
7+
const body = urlencoded({ extended: false });
8+
9+
module.exports = (app, provider) => {
10+
const { constructor: { errors: { SessionNotFound } } } = provider;
11+
12+
app.use((req, res, next) => {
13+
const orig = res.render;
14+
// you'll probably want to use a full blown render engine capable of layouts
15+
res.render = (view, locals) => {
16+
app.render(view, locals, (err, html) => {
17+
if (err) throw err;
18+
orig.call(res, '_layout', {
19+
...locals,
20+
body: html,
21+
});
22+
});
23+
};
24+
next();
25+
});
26+
27+
app.get('/interaction/:grant', async (req, res, next) => {
28+
try {
29+
const details = await provider.interactionDetails(req);
30+
const client = await provider.Client.find(details.params.client_id);
31+
32+
if (details.interaction.error === 'login_required') {
33+
return res.render('login', {
34+
client,
35+
details,
36+
title: 'Sign-in',
37+
params: querystring.stringify(details.params, ',<br/>', ' = ', {
38+
encodeURIComponent: value => value,
39+
}),
40+
interaction: querystring.stringify(details.interaction, ',<br/>', ' = ', {
41+
encodeURIComponent: value => value,
42+
}),
43+
});
44+
}
45+
return res.render('interaction', {
46+
client,
47+
details,
48+
title: 'Authorize',
49+
params: querystring.stringify(details.params, ',<br/>', ' = ', {
50+
encodeURIComponent: value => value,
51+
}),
52+
interaction: querystring.stringify(details.interaction, ',<br/>', ' = ', {
53+
encodeURIComponent: value => value,
54+
}),
55+
});
56+
} catch (err) {
57+
return next(err);
58+
}
59+
});
60+
61+
app.post('/interaction/:grant/confirm', body, async (req, res, next) => {
62+
try {
63+
const result = { consent: {} };
64+
await provider.interactionFinished(req, res, result);
65+
} catch (err) {
66+
next(err);
67+
}
68+
});
69+
70+
app.post('/interaction/:grant/login', body, async (req, res, next) => {
71+
try {
72+
const account = await Account.findByLogin(req.body.login);
73+
74+
const result = {
75+
login: {
76+
account: account.accountId,
77+
acr: 'urn:mace:incommon:iap:bronze',
78+
amr: ['pwd'],
79+
remember: !!req.body.remember,
80+
ts: Math.floor(Date.now() / 1000),
81+
},
82+
consent: {},
83+
};
84+
85+
await provider.interactionFinished(req, res, result);
86+
} catch (err) {
87+
next(err);
88+
}
89+
});
90+
91+
app.use((err, req, res, next) => {
92+
if (err instanceof SessionNotFound) {
93+
// handle interaction expired / session not found error
94+
}
95+
next(err);
96+
});
97+
};

example/routes/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module.exports.koa = require('./koa');
2+
module.exports.express = require('./express');

0 commit comments

Comments
 (0)