Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
@jmondi/oauth2-server
Advanced tools
[![GitHub Workflow Status]( https://img.shields.io/github/actions/workflow/status/jasonraimondi/ts-oauth2-server/build-and-test.yml?branch=main&style=flat-square)](https://github.com/jasonraimondi/ts-oauth2-server) [![Test Coverage](https://img.shields.io
@jmondi/oauth2-server
is a standards compliant implementation of an OAuth 2.0 authorization server for Node, written in TypeScript.
Requires node >= 18
The following RFCs are implemented:
Out of the box it supports the following grants:
Any framework should work, here are example adapters for Express and Fastify.
Example implementations:
The included adapters are just helper functions, really any framework should be supported. Take a look at the adapter implementations for express and fastify to learn how you can implement one for your favorite tool!
Save some eye strain, use the documentation site
pnpm add @jmondi/oauth2-server
Version | Latest Version | Security Updates |
---|---|---|
3.x | :tada: | :tada: |
2.x | :tada: |
The server uses two endpoints, GET /authorize
and POST /token
.
The Token Endpoint is a back channel endpoint that issues a use-able access token.
The Authorize Endpoint is a front channel endpoint that issues an authorization code. The
authorization code can then be exchanged to the AuthorizationServer
endpoint for a use-able access token.
import {
handleExpressResponse,
handleExpressError,
} from "@jmondi/oauth2-server/express";
app.post("/token", async (req: Express.Request, res: Express.Response) => {
const request = requestFromExpress(req);
try {
const oauthResponse = await authorizationServer.respondToAccessTokenRequest(request);
return handleExpressResponse(res, oauthResponse);
} catch (e) {
handleExpressError(e, res);
return;
}
});
The /authorize
endpoint is a front channel endpoint that issues an authorization code. The authorization code can then be exchanged to the AuthorizationServer
endpoint for a useable access token.
The endpoint should redirect the user to login, and then to accept the scopes requested by the application, and only when the user accepts, should it send the user back to the clients redirect uri.
We are able to add in scope acceptance and 2FA into our authentication flow.
import { requestFromExpress } from "@jmondi/oauth2-server/express";
app.get("/authorize", async (req: Express.Request, res: Express.Response) => {
const request = requestFromExpress(req);
try {
// Validate the HTTP request and return an AuthorizationRequest.
const authRequest = await authorizationServer.validateAuthorizationRequest(request);
// You will probably redirect the user to a login endpoint.
if (!req.user) {
res.redirect("/login")
return;
}
// After login, the user should be redirected back with user in the session.
// You will need to manage the authorization query on the round trip.
// The auth request object can be serialized and saved into a user's session.
// Once the user has logged in set the user on the AuthorizationRequest
authRequest.user = req.user;
// Once the user has approved or denied the client update the status
// (true = approved, false = denied)
authRequest.isAuthorizationApproved = getIsAuthorizationApprovedFromSession();
// If the user has not approved the client's authorization request,
// the user should be redirected to the approval screen.
if (!authRequest.isAuthorizationApproved) {
// This form will ask the user to approve the client and the scopes requested.
// "Do you authorize Jason to: read contacts? write contacts?"
res.redirect("/scopes")
return;
}
// At this point the user has approved the client for authorization.
// Any last authorization requests such as Two Factor Authentication (2FA) can happen here.
// Redirect back to redirect_uri with `code` and `state` as url query params.
const oauthResponse = await authorizationServer.completeAuthorizationRequest(authRequest);
return handleExpressResponse(res, oauthResponse);
} catch (e) {
handleExpressError(e, res);
}
});
The AuthorizationServer depends on the repositories. By default, no grants are enabled; each grant is opt-in and must be enabled when creating the AuthorizationServer.
You can enable any grant types you would like to support.
const authorizationServer = new AuthorizationServer(
clientRepository,
accessTokenRepository,
scopeRepository,
new JwtService("secret-key"),
);
// Enable as many or as few grants as you'd like.
authorizationServer.enableGrantTypes(
"client_credentials",
"refresh_token",
);
// with custom token TTL
authorizationServer.enableGrantTypes(
["client_credentials", new DateInterval("1d")],
["refresh_token", new DateInterval("1d")],
);
There are a few repositories you are going to need to implement in order to create an AuthorizationServer
.
And a few entities.
Grants are different ways a client can obtain an access_token
that will authorize it to use the resource server.
Deciding which grant to use depends on the type of client the end user will be using.
+-------+
| Start |
+-------+
V
|
|
+------------------------+ +-----------------------+
| Have a refresh token? |>----Yes----->| Refresh Token Grant |
+------------------------+ +-----------------------+
V
|
No
|
+---------------------+
| Who is the | +--------------------------+
| Access token owner? |>---A Machine---->| Client Credentials Grant |
+---------------------+ +--------------------------+
V
|
|
A User
|
|
+----------------------+
| What type of client? |
+----------------------+
|
| +---------------------------+
|>-----------Server App---------->| Auth Code Grant with PKCE |
| +---------------------------+
|
| +---------------------------+
|>-------Browser Based App------->| Auth Code Grant with PKCE |
| +---------------------------+
|
| +---------------------------+
|>-------Native Mobile App------->| Auth Code Grant with PKCE |
+---------------------------+
When applications request an access token to access their own resources, not on behalf of a user.
The client sends a POST to the /token
endpoint with the following body:
client_credentials
The authorization server will respond with the following response.
Bearer
A temporary code that the client will exchange for an access token. The user authorizes the application, they are redirected back to the application with a temporary code in the URL. The application exchanges that code for the access token.
The client redirects the user to the /authorize
with the following query parameters:
code
plain
or S256
, depending on whether the challenge is the plain verifier string or the SHA256 hash of the string. If this parameter is omitted, the server will assume plain.The user will be asked to login to the authorization server and approve the client and requested scopes.
If the user approves the client, they will be redirected from the authorization server to the provided redirect_uri
with the following fields in the query string:
The client sends a POST to the /token
endpoint with the following body:
authorization_code
The authorization server will respond with the following response
Bearer
The code_verifier
is part of the extended “PKCE” and helps mitigate the threat of having authorization codes intercepted.
Before initializing Part One of the authorization code flow, the client first creats a code_verifier
. This is a cryptographically random string using the characters A-Z, a-z, 0-9, and the punctuation characters -._~
(hyphen, period, underscore, and tilde), between 43 and 128 characters long.
We can do this in Node using the native crypto package and a base64urlencode
function:
import crypto from "node:crypto";
const code_verifier = crypto.randomBytes(43).toString("hex");
https://www.oauth.com/oauth2-servers/pkce/authorization-request/
Now we need to create a code_challenge
from our code_verifier
.
For devices that can perform a SHA256 hash, the code challenge is a BASE64-URL-encoded string of the SHA256 hash of the code verifier.
const code_challenge = base64urlencode(
crypto.createHash("sha256")
.update(code_verifier)
.digest()
);
Clients that do not have the ability to perform a SHA256 hash are permitted to use the plain code_verifier
string as the code_challenge
.
const code_challenge = code_verifier;
Access tokens eventually expire. The refresh token grant enables the client to obtain a new access_token from an existing refresh_token.
A complete refresh token request will include the following parameters:
refresh_token
The authorization server will respond with the following response
Bearer
The Password Grant is for first party clients that are able to hold secrets (ie not Browser or Native Mobile Apps)
A complete refresh token request will include the following parameters:
password
The authorization server will respond with the following response
Bearer
This grant is supported in the AuthorizationServer, but not recommended to use and thus is not documented. Industry best practice recommends using the Authorization Code Grant w/ PKCE for clients such as native and browser-based apps.
Please look at these great resources:
Note: Implementing this endpoint is optional.
The /token/revoke
endpoint is a back channel endpoint that revokes an existing token. Implementing this endpoint is optional.
app.post("/token/revoke", async (req: Express.Request, res: Express.Response) => {
try {
const oauthResponse = await authorizationServer.revoke(req);
return handleExpressResponse(res, oauthResponse);
} catch (e) {
handleExpressError(e, res);
return;
}
});
This project is inspired by the PHP League's OAuth2 Server. Check out the PHP League's other packages for some other great PHP projects.
FAQs
[![JSR](https://jsr.io/badges/@jmondi/oauth2-server?style=flat-square)](https://jsr.io/@jmondi/oauth2-server) [![NPM Version](https://img.shields.io/npm/v/%40jmondi%2Foauth2-server?style=flat-square)](https://www.npmjs.com/package/@jmondi/oauth2-server) [
We found that @jmondi/oauth2-server demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.