@myazarc/elysia-oauth2-server
v0.0.1
Published
A customized OAuth 2.0 server builder for Elysia
Downloads
359
Maintainers
Readme
OAuth2 Server implementation for Elysia
An extension for quickly and easily developing an OAuth2 server with Elysia.
It has been developed based on the express-oauth-server module using the @node-oauth/oauth2-server module.
Complete, compliant and well tested module for implementing an OAuth2 Server/Provider with Elysia in Bun.
This is the Elysia wrapper for oauth2-server. :)
Installation
bun add @myazarc/elysia-oauth2-server
Supported flows
- password
- authorization_code
- client_credentials
- refresh_token
for details: https://oauth.net/2/grant-types/
Using
Step 1: Choose the flow you will use and create the model.
const clients: Client[] = [
{
id: 'clientId1',
clientSecret: 'client@secret',
redirectUris: ['http://localhost:3000'],
// only those allowed
grants: [
'password',
'authorization_code',
'client_credentials',
'refresh_token',
],
},
];
const users = [{ id: '123', username: 'mira', password: '12345' }];
const tokens: any[] = [];
const codes: any[] = [];
const oauth2Opts: Oauth2Options = {
model: {
//#region all
getAccessToken: async (accessToken: string) => {
return tokens.find((token) => token.accessToken === accessToken);
},
getClient: async (clientId: string, clientSecret: string) => {
if (clientSecret === null) {
const client = clients.find((client) => client.id === clientId);
return client;
}
const client = clients.find(
(client) =>
client.id === clientId && client.clientSecret === clientSecret,
);
return client;
},
getUser: async (username: string, password: string) => {
return users.find(
(user) => user.username === username && user.password === password,
);
},
saveToken: async (token: any, client: any, user: any) => {
const tokenData: Token = {
accessToken: token.accessToken,
accessTokenExpiresAt: token.accessTokenExpiresAt,
refreshToken: token.refreshToken,
refreshTokenExpiresAt: token.refreshTokenExpiresAt,
client: {
id: client.id,
grants: client.grants,
},
user: {
id: user.id,
},
};
tokens.push(tokenData);
return tokenData;
},
//#endregion
//#region authorization code
saveAuthorizationCode: async (code: any, client: any, user: any) => {
codes.push({
...code,
client: {
id: client.id,
grants: client.grants,
},
user: {
id: user.id,
},
});
return code;
},
getAuthorizationCode: async (authorizationCode: string) => {
const a = codes.find(
(code) => code.authorizationCode === authorizationCode,
);
return a;
},
revokeAuthorizationCode: async (code: any) => {
const index = codes.findIndex(
(c) => c.authorizationCode === code.authorizationCode,
);
codes.splice(index, 1);
return true;
},
//#endregion
//#region client credentials
getUserFromClient: async (client: any) => {
// TODO: find related user
return users[0];
},
//#endregion
//#region refresh token
getRefreshToken: async (refreshToken: string) => {
return tokens.find((token) => token.refreshToken === refreshToken);
},
revokeToken: async (token: any) => {
const index = tokens.findIndex(
(t) => t.accessToken === token.accessToken,
);
tokens.splice(index, 1);
return true;
},
//#endregion
},
};
Step 2: Integration with Elysia and adding routes.
new Elysia()
.use(oauth2(oauth2Opts)) // oauth2 wrapper
.post(
// getting token
'/oauth2/token',
({ oauth2, ...payload }) => {
return oauth2.token(payload);
},
{
headers: t.Object({
// Example: "Basic " + Buffer.from("clientId1:client@secret").toString("base64"),
authorization: t.String(),
}),
body: t.Object({
username: t.Optional(t.String()),
password: t.Optional(t.String()),
grant_type: t.String({
examples: [
'password',
'refresh_token',
'client_credentials',
'authorization_code',
],
}),
code: t.Optional(t.String()),
redirect_uri: t.Optional(t.String()),
refresh_token: t.Optional(t.String()),
}),
},
)
.post(
// verifing token
'/oauth2/authenticate',
({ oauth2, ...payload }) => {
return oauth2.authenticate(payload);
},
{
headers: t.Object({
// Example: "Basic " + Buffer.from("clientId1:client@secret").toString("base64"),
authorization: t.String(),
}),
body: t.Object({
token: t.String(),
}),
},
)
.post(
// checking credentials
'/oauth2/authorize',
({ oauth2, ...payload }) => {
return oauth2.authorize(payload, {
// TODO: implement.
authenticateHandler: {
handle: (request: any) => {
return { id: '123' };
},
},
});
},
{
body: t.Object({
client_id: t.String(),
response_type: t.String({
examples: ['code', 'token'],
}),
state: t.String(),
redirect_uri: t.String(),
}),
},
)
.listen(3000);
Full Code
import { Elysia, t } from 'elysia';
import {
Oauth2Options,
oauth2,
Client,
Token,
} from '@myazarc/elysia-oauth2-server';
const clients: Client[] = [
{
id: 'clientId1',
clientSecret: 'client@secret',
redirectUris: ['http://localhost:3000'],
// only those allowed
grants: [
'password',
'authorization_code',
'client_credentials',
'refresh_token',
],
},
];
const users = [{ id: '123', username: 'mira', password: '12345' }];
const tokens: any[] = [];
const codes: any[] = [];
const oauth2Opts: Oauth2Options = {
model: {
//#region all
getAccessToken: async (accessToken: string) => {
return tokens.find((token) => token.accessToken === accessToken);
},
getClient: async (clientId: string, clientSecret: string) => {
if (clientSecret === null) {
const client = clients.find((client) => client.id === clientId);
return client;
}
const client = clients.find(
(client) =>
client.id === clientId && client.clientSecret === clientSecret,
);
return client;
},
getUser: async (username: string, password: string) => {
return users.find(
(user) => user.username === username && user.password === password,
);
},
saveToken: async (token: any, client: any, user: any) => {
const tokenData: Token = {
accessToken: token.accessToken,
accessTokenExpiresAt: token.accessTokenExpiresAt,
refreshToken: token.refreshToken,
refreshTokenExpiresAt: token.refreshTokenExpiresAt,
client: {
id: client.id,
grants: client.grants,
},
user: {
id: user.id,
},
};
tokens.push(tokenData);
return tokenData;
},
//#endregion
//#region authorization code
saveAuthorizationCode: async (code: any, client: any, user: any) => {
codes.push({
...code,
client: {
id: client.id,
grants: client.grants,
},
user: {
id: user.id,
},
});
return code;
},
getAuthorizationCode: async (authorizationCode: string) => {
const a = codes.find(
(code) => code.authorizationCode === authorizationCode,
);
return a;
},
revokeAuthorizationCode: async (code: any) => {
const index = codes.findIndex(
(c) => c.authorizationCode === code.authorizationCode,
);
codes.splice(index, 1);
return true;
},
//#endregion
//#region client credentials
getUserFromClient: async (client: any) => {
// TODO: find related user
return users[0];
},
//#endregion
//#region refresh token
getRefreshToken: async (refreshToken: string) => {
return tokens.find((token) => token.refreshToken === refreshToken);
},
revokeToken: async (token: any) => {
const index = tokens.findIndex(
(t) => t.accessToken === token.accessToken,
);
tokens.splice(index, 1);
return true;
},
//#endregion
},
};
new Elysia()
.use(oauth2(oauth2Opts)) // oauth2 wrapper
.post(
// getting token
'/oauth2/token',
({ oauth2, ...payload }) => {
return oauth2.token(payload);
},
{
headers: t.Object({
// Example: "Basic " + Buffer.from("clientId1:client@secret").toString("base64"),
authorization: t.String(),
}),
body: t.Object({
username: t.Optional(t.String()),
password: t.Optional(t.String()),
grant_type: t.String({
examples: [
'password',
'refresh_token',
'client_credentials',
'authorization_code',
],
}),
code: t.Optional(t.String()),
redirect_uri: t.Optional(t.String()),
refresh_token: t.Optional(t.String()),
}),
},
)
.post(
// verifing token
'/oauth2/authenticate',
({ oauth2, ...payload }) => {
return oauth2.authenticate(payload);
},
{
headers: t.Object({
// Example: "Basic " + Buffer.from("clientId1:client@secret").toString("base64"),
authorization: t.String(),
}),
body: t.Object({
token: t.String(),
}),
},
)
.post(
// checking credentials
'/oauth2/authorize',
({ oauth2, ...payload }) => {
return oauth2.authorize(payload, {
// TODO: implement.
authenticateHandler: {
handle: (request: any) => {
return { id: '123' };
},
},
});
},
{
body: t.Object({
client_id: t.String(),
response_type: t.String({
examples: ['code', 'token'],
}),
state: t.String(),
redirect_uri: t.String(),
}),
},
)
.listen(3000);