https://github.com/austinjhunt/express-oauth-oidc-demo
Science Score: 13.0%
This score indicates how likely this project is to be science-related based on various indicators:
-
○CITATION.cff file
-
✓codemeta.json file
Found codemeta.json file -
○.zenodo.json file
-
○DOI references
-
○Academic publication links
-
○Academic email domains
-
○Institutional organization owner
-
○JOSS paper metadata
-
○Scientific vocabulary similarity
Low similarity (10.0%) to scientific vocabulary
Repository
Basic Info
- Host: GitHub
- Owner: austinjhunt
- Language: JavaScript
- Default Branch: main
- Size: 1.72 MB
Statistics
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
- Releases: 0
Metadata Files
README.md
Demonstration of Google's OIDC-conformant OAuth 2.0 Flow with Express and Google Drive
This is a simple lightweight app for demonstrating use of Google's OAuth 2.0 APIs (which conform to the OIDC specification) for authN and authZ with Express and Google Drive.
Overview
The goal of this lightweight ExpressJS app is to demonstrate the OpenID Connect Authorization flow against a Google Cloud Platform application created with very restricted read-only Google Drive scopes. More specifically, it follows the official Google OAuth 2.0 implementation for authentication which happens to conform to the OpenID Connect specification, and is OpenID Certified. In short, Google's OAuth 2.0 APIs can be used for both authentication (OIDC) and authorization (OAuth).
Implementation
GCP
First things first, I created a new project in Google called Simple OAuth Demo, configured a consent screen for it with authorized JS origin http://localhost:8080 and redirect URL http://localhost:8080/redirect_uri (which is the Express route that handles the redirect back from the consent screen). I then created an OAuth client ID in the credentials screen within the project, and downloaded the credentials JSON file (stored as a gitignored config.js in the repo).
From here, I moved to the Express development.
Express App
The app was implemented with the simple Express web framework for Node.js.
When run, you're first presented with a basic home page with a button that when clicked will trigger the OIDC-conforming OAuth 2.0 authorization flow documented by Google.

When you click the button (which is really a link to the /start-flow GET route), behind the scenes, the first thing that is happening is the generation of a random anti-forgery state token such that this value can be compared with the state value returned after Google redirects the user back to the app.
Then, the user gets redirected to the authentication endpoint on Google with a series of query parameters as defined below, coming from the (gitignored) config file pulled from GCP when creating the OAuth client. I made sure to include access_type: offline in this particular request to allow for the use of a refresh token without involving the user when the access token expires.
app.get("/start-flow", (req, res) => {
let state = getRandomValue(24);
req.session.state = state;
let full_auth_url =
`${config.auth_uri}?` +
serialize({
response_type: "code", // should always be code for basic auth code flow
client_id: config.client_id,
access_type: "offline", // allow refresh token
scope: scopes.join(" "),
redirect_uri: config.redirect_uris[0],
state: req.session.state,
nonce: getRandomValue(24), // random value for replay protection
});
res.redirect(full_auth_url);
});
When redirected, the user gets prompted first with a login screen if not already logged into Google (the identity provider).

After logging in (ideally with MFA configured on the Google Account), the user then gets a warning about the client being unverified by Google which is definitely true since this is very much a small-scale test application.

If the user clicks Continue, they then get prompted with the OAuth Consent Screen configured during the GCP setup. This page outlines every permission (scope) being requested by the client, and allows the user to check the specific permissions they want to grant to the client.

After the consent screen, Google then redirects the user back to the configured redirect_uri for the client which in the case of this app is the /redirect_uri route handled by the following code in index.js.
```
app.get("/redirecturi", (req, res) => {
// redirect URI configured on GCP to which
// auth endpoint response is sent after user consents
// verify that state in query param matches session state generated before
// sending user to auth endpoint
if (req.query.state === req.session.state) {
console.log("state matches");
req.session.authorized = true;
exchangeAuthCodeForAccessToken(req, res).then(() => {
fetch("https://www.googleapis.com/drive/v3/about?fields=user", {
method: "GET",
headers: {
Authorization: `Bearer ${req.session.accesstoken},
Accept: "application/json",
},
})
.then((response) => response.json())
.then((data) => {
if (data.user) {
console.log(data.user.emailAddress);
req.session.user = data.user;
req.session.emailAddress = data.user.emailAddress;
req.session.photoLink = data.user.photoLink;
req.session.displayName = data.user.displayName;
}
})
.then(() => {
res.render("index", {
authorized: req.session.authorized,
emailAddress: req.session.emailAddress,
photoLink: req.session.photoLink,
displayName: req.session.displayName,
});
});
});
} else {
console.log(Returned state does not match session state );
res.redirect("/start-flow");
}
});
``
Note that in this example we're assuming that the user has consented to the requested permissions/scopes before getting to this point, but usually this is the point where you want to check which permissions were granted and which ones were not such that portions of your system or application using denied scopes can be disabled for the user.
When redirected to /redirect_uri by Google, two important query parameters are included: state and code. The first thing that happens here is the verification of the state token by comparing it against the state token which was previously generated when starting the flow. If the token matches, we take the code query parameter (which is a one-time authorization code) and exchange it with Google's token endpoint for an access token and an ID token. We also update the user's session to indicate the authorization. Moreover, we use the newly obtained access token to hit the About API endpoint to pull some basic Google Account information about the user (namely first name and email address).
With that information pulled from Google via the API, EJS is then used to embed that information into the front end view. EJS is also used to conditionally show a button for pulling Google Drive File Information for users who are authorized (by embedding the boolean authorized session variable into the EJS view). If the user is authorized, they see the button (really a link to the /get-files route) to pull the basic metadata about their Google Drive Files.
When clicked, the /get-files route handler on the server side executes and pulls a files list from the Google Drive v3 API using the access token stored in the session. For each of the returned files, the JavaScript on the client site appends a basic container element with the file's name and mimetype displayed.

At this point, the user has consented to the app reading information from the user's Google account. The user can confirm this by looking at a number of screens on their Google Account:
Activity on Google Account

Third party apps with access

The user can optionally revoke that access as shown below:

Running the App
- Clone the repository and navigate into it via CLI.
- Run the following commands:
npm i
node index.js
Owner
- Name: Austin Hunt
- Login: austinjhunt
- Kind: user
- Location: Greenville, SC
- Company: College of Charleston
- Website: https://austinjhunt.com
- Twitter: austinsketches
- Repositories: 20
- Profile: https://github.com/austinjhunt
Portrait-artist-turned-computer-geek with a fused love for the visual and the technical, bringing experience with and excitement for web dev, automation, & art
GitHub Events
Total
Last Year
Issues and Pull Requests
Last synced: over 1 year ago
All Time
- Total issues: 0
- Total pull requests: 0
- Average time to close issues: N/A
- Average time to close pull requests: N/A
- Total issue authors: 0
- Total pull request authors: 0
- Average comments per issue: 0
- Average comments per pull request: 0
- Merged pull requests: 0
- Bot issues: 0
- Bot pull requests: 0
Past Year
- Issues: 0
- Pull requests: 0
- Average time to close issues: N/A
- Average time to close pull requests: N/A
- Issue authors: 0
- Pull request authors: 0
- Average comments per issue: 0
- Average comments per pull request: 0
- Merged pull requests: 0
- Bot issues: 0
- Bot pull requests: 0