-
Notifications
You must be signed in to change notification settings - Fork 4
Add example for Authorization Code grant. #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
shrihari-prakash
wants to merge
10
commits into
main
Choose a base branch
from
authorization-code
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 4 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
58b1138
Added authoriation code example.
shrihari-prakash c9bdfa9
Add package lock and cors.
shrihari-prakash 8f8ac66
Add client package lock.
shrihari-prakash 6fefdd4
Remove eslintrc.
shrihari-prakash 091afdf
Fix registry.
shrihari-prakash 52efed2
Add client env variables to README.
shrihari-prakash 7adf469
PR comments.
shrihari-prakash da94b0e
Fix dotenv.
shrihari-prakash fc12a79
Fix styling after auth.
shrihari-prakash 9fa8330
Simplify styles.
shrihari-prakash File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# Authorization Code Grant Example | ||
|
||
## Architecture | ||
|
||
The authorization code workflow is described in | ||
[RFC 6749, section 4.1](https://datatracker.ietf.org/doc/html/rfc6749.html#section-4.1): | ||
|
||
``` | ||
+----------+ | ||
| Resource | | ||
| Owner | | ||
| | | ||
+----------+ | ||
^ | ||
| | ||
(B) | ||
+----|-----+ Client Identifier +---------------+ | ||
| -+----(A)-- & Redirection URI ---->| | | ||
| User- | | Authorization | | ||
| Agent -+----(B)-- User authenticates --->| Server | | ||
| | | | | ||
| -+----(C)-- Authorization Code ---<| | | ||
+-|----|---+ +---------------+ | ||
| | ^ v | ||
(A) (C) | | | ||
| | | | | ||
^ v | | | ||
+---------+ | | | ||
| |>---(D)-- Authorization Code ---------' | | ||
| Client | & Redirection URI | | ||
| | | | ||
| |<---(E)----- Access Token -------------------' | ||
+---------+ (w/ Optional Refresh Token) | ||
``` | ||
|
||
### Provider dependencies | ||
|
||
- @node-oauth/express-oauth-server (uses @node-oauth/oauth2-server) | ||
- express | ||
- body-parser | ||
|
||
### Client dependencies | ||
|
||
- express | ||
- ejs | ||
|
||
## Installation and usage | ||
|
||
Install dependencies in both provider and client directories: | ||
|
||
```shell | ||
$ cd provider && npm install | ||
$ cd ../client && npm install | ||
``` | ||
|
||
Create a `.env` file in the authorization-code directory: | ||
|
||
``` | ||
CLIENT_ID=testclient | ||
CLIENT_SECRET=testsecret | ||
REDIRECT_URI=http://localhost:3000/callback | ||
USER_ID=user1 | ||
USERNAME=demo | ||
PASSWORD=demo | ||
``` | ||
|
||
Start the provider (authorization server + resource server): | ||
|
||
```shell | ||
$ cd provider && npm start | ||
``` | ||
|
||
Start the client application: | ||
|
||
```shell | ||
$ cd client && npm start | ||
``` | ||
|
||
Visit http://localhost:3000 to start the authorization code flow. | ||
|
||
## About This Example | ||
|
||
This example demonstrates a clear separation between the OAuth2 provider (authorization server + resource server) and the client application. Unlike other examples that might combine both roles in a single application, this example shows: | ||
|
||
- **Provider** (port 8080): Acts as both authorization server and resource server | ||
- **Client** (port 3000): A separate web application that consumes OAuth2 services | ||
|
||
This separation makes it easier to understand what the framework supports and what it doesn't. | ||
|
||
## Flow | ||
|
||
1. User visits the client application at http://localhost:3000 | ||
2. User clicks "Login" to start the authorization flow | ||
3. User is redirected to the provider's authorization page | ||
4. User enters credentials and grants authorization | ||
5. User is redirected back to the client with an authorization code | ||
6. Client exchanges the code for an access token | ||
7. Client can now access protected resources using the access token |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
require("dotenv").config({ path: "../.env" }); | ||
const express = require("express"); | ||
const crypto = require("crypto"); | ||
|
||
const app = express(); | ||
const states = new Map(); | ||
|
||
app.set("view engine", "ejs"); | ||
app.set("views", "./views"); | ||
app.use(express.static("public")); | ||
|
||
const authServer = "http://localhost:8080"; | ||
const clientId = process.env.CLIENT_ID || "testclient"; | ||
const clientSecret = process.env.CLIENT_SECRET || "testsecret"; | ||
const redirectUri = | ||
process.env.REDIRECT_URI || "http://localhost:3000/callback"; | ||
|
||
function generateState() { | ||
return crypto.randomBytes(16).toString("hex"); | ||
} | ||
|
||
app.use(express.urlencoded({ extended: false })); | ||
app.use(express.json()); | ||
|
||
app.get("/", (req, res) => { | ||
res.render("index", { | ||
authServer: authServer, | ||
}); | ||
}); | ||
|
||
app.get("/login", (req, res) => { | ||
const state = generateState(); | ||
states.set(state, { created: Date.now() }); | ||
|
||
res.render("authorize", { | ||
client: { id: clientId }, | ||
redirectUri: redirectUri, | ||
scope: "read write", | ||
state: state, | ||
authServer: authServer, | ||
}); | ||
}); | ||
|
||
app.get("/callback", (req, res) => { | ||
const { code, state, error } = req.query; | ||
|
||
if (error) { | ||
return res.render("error", { | ||
message: `Authorization Error: ${error}`, | ||
}); | ||
} | ||
|
||
if (!states.has(state)) { | ||
return res.render("error", { | ||
message: "Invalid State: State parameter mismatch", | ||
}); | ||
} | ||
|
||
states.delete(state); | ||
|
||
res.render("callback", { | ||
code: code, | ||
state: state, | ||
authServer: authServer, | ||
clientId: clientId, | ||
clientSecret: clientSecret, | ||
redirectUri: redirectUri, | ||
}); | ||
}); | ||
|
||
app.get("/logout", (req, res) => { | ||
res.redirect("/"); | ||
}); | ||
|
||
app.listen(3000); | ||
console.debug("[Client]: listens to http://localhost:3000"); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if the sample worked just as is without any envs. So I added a default, but we can discuss the options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think envs are fine, you can also use
.env
if you like, it's a good common practiceThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did add both options. Use the values from the defaults if the env does not exist. But I am happy to remove the defaults if you prefer it to come only from the env.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or should we commit a
.env
file with the defaults? Not like it contains secretive stuff... The intention being the samples work with as minimal effort as possible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
common practice is to gitignore .env but provide a
.env.example
that users can copy. This avoids ever checking in real .env (in case users build their project upon the example)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or do not check in a
.env
at all but tell users to create one with example data. My concerns are only regarding users cloning and continuing to use it until production.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a
.env.example
file. Seems to be a good balance incase they use it till production.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jankapunkt FYI I got the example to a runnable state. Would you be able to run this when you have some availability?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shrihari-prakash yes I will test it and leave comments/review if needed