{"id":18550508,"url":"https://github.com/srmoore/oidc_nodejs_demo","last_synced_at":"2025-12-30T23:04:59.994Z","repository":{"id":82812349,"uuid":"104136602","full_name":"srmoore/oidc_nodejs_demo","owner":"srmoore","description":"Small demo Node.js Express application using private keys and OpenID Connect","archived":false,"fork":false,"pushed_at":"2017-09-20T12:57:07.000Z","size":38,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-17T10:25:47.363Z","etag":null,"topics":["expressjs","nodejs","oidc","tutorial"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/srmoore.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-09-19T22:38:55.000Z","updated_at":"2018-01-04T19:39:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"371e000d-a224-4cdc-b370-0355f6c32c3e","html_url":"https://github.com/srmoore/oidc_nodejs_demo","commit_stats":{"total_commits":10,"total_committers":1,"mean_commits":10.0,"dds":0.0,"last_synced_commit":"a5954d9dbe83fd5578e5cce4ee6ec4c81f37d31e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srmoore%2Foidc_nodejs_demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srmoore%2Foidc_nodejs_demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srmoore%2Foidc_nodejs_demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srmoore%2Foidc_nodejs_demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/srmoore","download_url":"https://codeload.github.com/srmoore/oidc_nodejs_demo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254319720,"owners_count":22051075,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["expressjs","nodejs","oidc","tutorial"],"created_at":"2024-11-06T21:04:59.352Z","updated_at":"2025-12-30T23:04:59.980Z","avatar_url":"https://github.com/srmoore.png","language":"JavaScript","readme":"# oidc_nodejs_demo\nSmall demo Node.js Express application using private keys and OpenID Connect\n\n## What you'll build\nYou'll build an Express web application with a login backed by OpenID Connect\n\n## What you'll need\n- [Node.js](https://nodejs.org/) : This guide uses v8.5.0\n \n## Create an unsecured web application\nOnce Node.js is installed, we're ready to setup our project.\nWe'll use the [Express Generator](https://expressjs.com/en/starter/generator.html) to set up the scafold of our project.\n\nInstall the generator globally\n`$ npm install express-generator -g`\n\nNext we'll generate our application using the [Pug template engine](https://github.com/pugjs/pug) (Which was formally called `Jade`). We'll change into the resulting directory and run `npm install` to finish setting up the dependencies.\n\n```\n $ express --view=pug myapp\n $ cd myapp\n myapp$ npm install\n```\n\nAt this point executing `DEBUG=myapp:* npm start` will start the server and you should be able to go to [http://localhost:3000/](http://localhost:3000) and see the Express landing page.\n\n## Generate RSA keys\nIf you are going to be using signed keys rather than a `client-secret` for authenticating your client to the OpenID Connect server, you'll need to gerenrate them. We'll be using the `node-jose` library to do this. **NOTE:** in order for the `node-jose` library to use `RSA256` keys, it requires all the optional parameters for the private key, and not just the required `d` parameter. To accomidate this, we'll use the `node-jose` library to generate our keys.\n\nInstall the `node-jose` library while in your `myapp` directory with:\n```\nmyapp$ npm install node-jose --save\n```\n\nNext you'll want to add the `generate_keys` file in `myapp/bin` and execute it to generate a public key in `pub_key.jwk` and the private key in `full_key.jwk`. Use the contents of the `pub_key.jwk` file when you register your client.\n\n```\n#!/usr/bin/env node\nconst jose = require('node-jose');\nconst fs = require('fs');\n\n// Set the properties used in generating the keys\nconst props = {\n\t\"kty\": \"RSA\",\n\t\"e\": \"AQAB\",\n\t\"kid\": \"my_rsa_key\", // set this to whatever you'd like your key id to be\n\t\"alg\": \"RS256\"\n};\n\nconst pub_key_file = '../pub_key.jwk';\nconst full_key_file = '../full_key.jwk';\n\n// Create a keystore\nkeystore = jose.JWK.createKeyStore();\n\n// Generate the key using 2048 bits\nkeystore.generate(\"RSA\", 2048, props)\n\t.then(result =\u003e {\n\t\tkey = result;\n\t\t// write out the public key\n\t\tfs.writeFileSync(pub_key_file, JSON.stringify({keys: [key.toJSON()]}));\n\t\t// write out the public/private key (DO NOT SHARE!)\n\t\tfs.writeFileSync(full_key_file, JSON.stringify({keys: [key.toJSON(true)]}));\n\t})\n.catch(err =\u003e {console.log(err);})\n```\n\n## Set up Passport and `openid-client`\n[Passport](http://passportjs.org/) makes it easy to add authentication to an Express application with a variaty of backends. Since we'll also want to make sure we're not re-logging in a user on every page hit, we'll want to add server side sessions with `express-session`.\n\n```\nmyapp $ npm install express-session --save\nmyapp $ npm install passport --save\n```\nNext we'll set these up by modifying `app.js`.\n\nNear the top, add in the require statements. \n\n```\n...\nvar app = express();\n\n// passport and sessions are required, so add them here.\n// You'll have to 'npm install' these.\nconst passport = require('passport');\nconst session = require('express-session');\n...\n```\n\n**NOTE** The express generator uses the `var` format from Node.js v6, which is ok, but we'll add in our new lines with v8 `const` syntax.\n\nA bit further down in `app.js` we'll initialize things. First the session management with the `app.use(session(...));` line. Then we'll initialize passport and tell it to use the sessions.\n\nLastly we'll tell passport how to serialize/deserialize users to the session, with a very simple function. (I imagine for more complicated applications these might be expanded.)\n\n```\n...\napp.use(cookieParser());\napp.use(express.static(path.join(__dirname, 'public')));\n\n// Here we'll set up using server side sessions\n// this is using an in memory system. Use something like memcached or redis in production\napp.use(session({ secret: 'ourapplicationsecret', // change this\n\t\t  resave: false,\n\t\t  saveUninitialized: false}));\n\n// initialize passport here\napp.use(passport.initialize());\napp.use(passport.session());\n\n// passport has to serialize/deserialize users to the sessions.\n//  This is a simple case\npassport.serializeUser((user, done) =\u003e { done(null, user);});\npassport.deserializeUser((user, done) =\u003e { done( null, user);});\n\napp.use('/', index);\napp.use('/users', users);\n...\n```\n\n### Set up `openid-client`\nNow we need to setup the openid-client which is how we'll connect to the openid-connect provider using the keys we generated earlier.\n\nFirst we'll install it\n```\nmyapp$ npm install openid-client --save\n```\n\nNext we'll add it to `app.js` and set up a `Strategy` for `passport` to use. Add the following after the passport `require()` statements you added before\n\n```\n// requirements for openid-client, as well as reading in the key we generated\nconst fs = require('fs'); // used to read in the key file\nconst jose = require('node-jose'); // used to parse the keystore\nconst Issue = require('openid-client').Issuer;\nconst Strategy = require('openid-client').Strategy;\n\n// filename for the keys\nconst jwk_file = \"./full_key.jwk\";\n\n// OpenID Connect provider url for discovery\nconst oidc_discover_url = \"https://mitreid.org\";\n\n// Params for creating the oidc client\nconst client_params = {\n        client_id: 'login-nodejs-govt-test',\n        token_endpoint_auth_method: 'private_key_jwt'\n};\n\n// Params for the OIDC Passport Strategy\nconst strat_params = {\n        redirect_uri: 'http://localhost:3000/openid-connect-login',\n        scope: 'openid profile email phone address',\n        response: ['userinfo']\n};\n```\n\nNext we'll actually set up the OIDC Strategy. Add the following after the passport serialization code we added above\n```\n// load keystore and set up openid-client\nconst jwk_json = JSON.parse(fs.readFileSync(jwk_file, \"utf-8\"));\n// load the keystore. returns a Promise\nlet load_keystore = jose.JWK.asKeyStore(jwk_json);\n// discover the Issuer. returns a Promise\n//   you can also set this up manually, see the project documentation\nlet discover_issuer = Issuer.discover(oidc_discover_url);\n\n// Create a client, and use it set up a Strategy for passport to use\n// since we need both the Issuer and the keystore, we'll use Promise.all()\nPromise.all([load_keystore, discover_issuer])\n        .then(([ks, myIssuer]) =\u003e {\n                console.log(\"Found Issuer: \", myIssuer);\n                const oidc_client = new myIssuer.Client(client_params, ks);\n                console.log(\"Created client: \", oidc_client);\n\n                // create a strategy along with the function that processes the results\n                passport.use('oidc', new Strategy({client: oidc_client, params: strat_params}, (tokenset, userinfo, done) =\u003e {\n                        // we're just loging out the tokens. Don't do this in production\n                        console.log('tokenset', tokenset);\n                        console.log('access_token', tokenset.access_token);\n                        console.log('id_token', tokenset.id_token);\n                        console.log('claims', tokenset.claims);\n                        console.log('userinfo', userinfo);\n\n                        // to do anything, we need to return a user. \n                        // if you are storing information in your application this would use some middlewhere and a database\n                        // the call would typically look like\n                        // User.findOrCreate(userinfo.sub, userinfo, (err, user) =\u003e { done(err, user); });\n                        // we'll just pass along the userinfo object as a simple 'user' object\n                        return done(null, userinfo);\n                }));\n        }) // close off the .then()\n        .catch((err) =\u003e {console.log(\"Error in OIDC setup\", err);});\n```\n\nNow all that is left is to set up the callback URL and add authentication requirements to protected URLs.\n\n### Setting up a protected page\n\nWe'll place all our authentication related routes in a single place `routes/authenticated_routes.js`. This file exports  a single function that adds routes to the app, along with the required `passport.autenticate(...)` calls. There is a second function in the file `isLoggedIn(req, res, next)` which is a helper function for locking down a new route. (In this case the `/profile` route).\n\n```\nmodule.exports = function(app, passport) {\n    app.get('/login', passport.authenticate('oidc'));\n\n    // OIDC Callback\n    app.get('/openid-connect-login', passport.authenticate('oidc', {successRedirect:'/profile', failureRedirect:'/'}));\n\n    app.get('/profile', isLoggedIn, function(req, res){\n        console.log(\"In profile\");\n        res.render('profile', {title: 'Express - profile', user: req.user.name});\n    });\n\n    app.get('/logout', function(req, res) {\n        req.logout();\n        res.redirect('/');\n    });\n};\n\nfunction isLoggedIn(req, res, next) {\n    if (req.isAuthenticated()) {\n        console.log(\"Is authenticated\");\n        return next();\n    }\n    console.log(\"Not Authenticated\");\n    res.redirect('/');\n}\n```\nNext we have to add this to the app.js file with the line:\n```\n// demo authentication routes\nconst authroutes = require('./routes/authenticated_routes')(app,passport);\n```\n\nThe only restriction on that line is it has to be after `app.use(passport.initialize());` is called, but it can be before we set up the Strategy.\n\nSince we're adding a `/profile` page, we'll need a view. To keep things simple, we'll just pass in the name from the user object and put it in the text.\n\nAdd `views/profile.pug`:\n```\nextends layout\n\nblock content\n  h1= title\n  p Welcome, #{user}\n\n  a(href=\"/logout\") Logout\n```\n**NOTE** Logging out will only log you out of the Express app. If you are still logged into the OpenID Connect Provider, you'll just get automatically logged back in by clicking `/login`.\n\nWhile we're at it, we'll add in a '/login' link to the index page. If we're sucessful on login, we'll get forwarded to the `/profile`.\n\nChange `views/index.pug` to:\n```\nextends layout\n\nblock content\n  h1= title\n  p Welcome to #{title}\n\n  a(href='/login') Login Here\n```\n\nNow start it up and hit [http://localhost:3000/](http://localhost:3000) with:\n```\n DEBUG=myapp:* npm start\n```\n\n## Summary\n - We set up an express app using the express generator (found in `initial_app/`)\n - We added the ability to generate secure keys for authenticating with an OpenID Connect provider using `node-jose`. (found in `generate_keys/`)\n - Lastly we integrated OpenID Connect with our express application using `openid-client` (found in `complete/`)\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrmoore%2Foidc_nodejs_demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsrmoore%2Foidc_nodejs_demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrmoore%2Foidc_nodejs_demo/lists"}