openid-client
openid-client is a server side OpenID Relying Party (RP, Client) implementation for
Node.js
Table of Contents
Implemented specs & features
The following client/RP features from OpenID Connect/OAuth2.0 specifications are implemented by
openid-client.
Example
Head over to the example folder to see the library in use. This example is deployed and configured
to use an example OpenID Connect Provider here. The provider is using
oidc-provider library.
Get started
On the off-chance you want to manage multiple clients for multiple issuers you need to first get
an Issuer instance.
via Discovery (recommended)
const Issuer = require('openid-client').Issuer;
Issuer.discover('https://accounts.google.com')
.then(function (googleIssuer) {
console.log('Discovered issuer %s', googleIssuer);
});
manually
const Issuer = require('openid-client').Issuer;
const googleIssuer = new Issuer({
issuer: 'https://accounts.google.com',
authorization_endpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
token_endpoint: 'https://www.googleapis.com/oauth2/v4/token',
userinfo_endpoint: 'https://www.googleapis.com/oauth2/v3/userinfo',
jwks_uri: 'https://www.googleapis.com/oauth2/v3/certs',
});
console.log('Set up issuer %s', googleIssuer);
Now you can create your Client.
manually (recommended)
You should provide the following metadata; client_id, client_secret
. You can also provide
id_token_signed_response_alg
(defaults to RS256
) and token_endpoint_auth_method
(defaults to
client_secret_basic
);
const client = new googleIssuer.Client({
client_id: 'zELcpfANLqY7Oqas',
client_secret: 'TQV5U29k1gHibH5bx1layBo0OSAvAbRT3UYW3EWrSYBB5swxjVfWUa1BS8lqzxG/0v9wruMcrGadany3'
});
via registration client uri
Should your oidc provider have provided you with a registration client uri and registration access
token you can also have the Client discovered.
new googleIssuer.Client.fromUri(registration_client_uri, registration_access_token)
.then(function (client) {
console.log('Discovered client %s', client);
});
Usage
Getting authorization url
client.authorizationUrl({
redirect_uri: 'https://client.example.com/callback',
scope: 'openid email',
});
You can also get HTML body of a self-submitting form to utilize POST to the authorization url with
#authorizationPost
method, same signature as #authorizationUrl
.
client.authorizationPost({
redirect_uri: 'https://client.example.com/callback',
scope: 'openid email',
});
Processing callback
client.authorizationCallback('https://client.example.com/callback', request.query)
.then(function (tokenSet) {
console.log('received tokens %j', tokenSet);
});
Processing callback with state or nonce check
const state = session.state;
const nonce = session.nonce;
client.authorizationCallback('https://client.example.com/callback', request.query, { state, nonce })
.then(function (tokenSet) {
console.log('received tokens %j', tokenSet);
});
Handling multiple response modes
When handling multiple response modes with one single pass you can use #authorizationParams
to get the params object from the koa/express/node request object or a url string.
(http.IncomingMessage). If form_post is your response_type you need to include a body parser prior.
client.authorizationParams('https://client.example.com/cb?code=code');
client.authorizationParams('/cb?code=code');
app.use(bodyParser({ patchNode: true }));
app.use(function* (next) {
const params = client.authorizationParams(this.request.req);
});
app.use(bodyParser.urlencoded({ extended: false }));
app.use(function (req, res, next) {
const params = client.authorizationParams(req);
});
Refreshing a token
client.refresh(refreshToken)
.then(function (tokenSet) {
console.log('refreshed tokens %j', tokenSet);
});
Tip: accepts TokenSet as well as direct refresh token values;
Revoke a token
client.revoke(token, [tokenTypeHint])
.then(function (response) {
console.log('revoked token %s', token, response);
});
Introspect a token
client.introspect(token, [tokenTypeHint])
.then(function (response) {
console.log('token details %j', response);
});
Fetching userinfo
client.userinfo(accessToken)
.then(function (userinfo) {
console.log('userinfo %j', userinfo);
});
Tip: accepts TokenSet as well as direct access token values;
via POST
client.userinfo(accessToken, { verb: 'post' });
auth via query
client.userinfo(accessToken, { via: 'query' });
auth via body
client.userinfo(accessToken, { verb: 'post', via: 'body' });
userinfo also handles (as long as you have the proper metadata configured) responses that are:
- signed
- signed and encrypted (nested JWT)
- just encrypted
Fetching Distributed Claims
let claims = {
sub: 'userID',
_claim_names: {
credit_history: 'src1',
email: 'src2',
},
_claim_sources: {
src1: { endpoint: 'https://src1.example.com/claims', access_token: 'foobar' },
src2: { endpoint: 'https://src2.example.com/claims' },
},
};
client.fetchDistributedClaims(claims, { src2: 'bearer.for.src2' })
.then(function (output) {
console.log('claims %j', claims);
console.log('output %j', output);
});
Unpacking Aggregated Claims
let claims = {
sub: 'userID',
_claim_names: {
credit_history: 'src1',
email: 'src2',
},
_claim_sources: {
src1: { JWT: 'probably.a.jwt' },
src2: { JWT: 'probably.another.jwt' },
},
};
client.unpackAggregatedClaims(claims)
.then(function (output) {
console.log('claims %j', claims);
console.log('output %j', output);
});
Custom token endpoint grants
Use when the token endpoint also supports client_credentials or password grants;
client.grant({
grant_type: 'client_credentials'
});
client.grant({
grant_type: 'password',
username: 'johndoe',
password: 'A3ddj3w',
});
Registering new client (via Dynamic Registration)
issuer.Client.register(metadata, [keystore])
.then(function (client) {
console.log('Registered client %s, %j', client, client.metadata);
});
WebFinger discovery
Issuer.webfinger(userInput)
.then(function (issuer) {
console.log('Discovered issuer %s', issuer);
});
Accepts, normalizes, discovers and validates the discovery of User Input using E-Mail, URL, acct,
Hostname and Port syntaxes as described in Discovery 1.0.
Uses already discovered (cached) issuers where applicable.
Configuration
Changing HTTP request defaults
Setting defaultHttpOptions
on Issuer
always merges your passed options with the default.
openid-client uses got for http requests with the following default request options
const DEFAULT_HTTP_OPTIONS = {
followRedirect: false,
headers: { 'User-Agent': `${pkg.name}/${pkg.version} (${pkg.homepage})` },
retries: 0,
timeout: 1500,
};
You can add your own headers, change the user-agent used or change the timeout setting
Issuer.defaultHttpOptions = { timeout: 2500, headers: { 'X-Your-Header': '<whatever>' } };
Confirm your httpOptions by
console.log('httpOptions %j', Issuer.defaultHttpOptions);