elysoid
v0.0.3
Published
Minimal, fully typesafe OpenID Elysia plugin for single page apps with stateless session management with JWT.
Downloads
9
Maintainers
Readme
Elysoid
Minimal, fully typesafe OpenID Elysia plugin for single page apps with stateless session management with JWT.
Basic setup
bun add elysoid
// plugin/auth.ts
import { Static, t } from "elysia"
import { IdTokenPayload, auth as authPlugin } from "elysoid"
const SessionSchema = t.Object({
id: t.String(),
roles: t.Array(t.String())
}) // create any session schema that you need for your app
type Session = Static<typeof SessionSchema>
// type IdTokenPayload = { sub: string, email?: string, name?: string }
const login = async (payload: IdTokenPayload): Promise<Session | null> => {
// Get user from DB, based on "sub" (or create)
// - with all required data for session
// - return null if not authorized to login
return {
id: "1",
roles: [ "admin" ]
}
}
export const auth = authPlugin(SessionSchema, login)
// index.ts
import { Elysia, t } from "elysia"
import { AuthenticationError, AuthorizationError } from "elysoid"
import { auth } from "./plugin/auth"
const app = new Elysia()
.use(auth) // /social-login endpoint registered - see eden treaty below
.post("/api/demo", ({ body, user }) => { // user is typed as Session | null
if (!user)
throw new AuthenticationError() // handled, will return 401
if (!user.roles.includes("admin"))
throw new AuthorizationError() // handled, will return 403
return { msg: `${body.data} received from ${user.id}` }
},
{
body: t.Object({ data: t.String() }),
response: t.Object({ msg: t.String() })
})
.listen(3000)
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
export type App = typeof app
# .env (or environment variables)
TOKEN_ENDPOINT=https://openidprovider.com/token
CLIENT_ID=yourlientid
CLIENT_SECRET=yourclientsecret
REDIRECT_URI=https://yourapp.com/callback
JWT_SECRET=shhhh
JWT_EXP=2h
LOG_LEVEL=trace
# and / or
AUTH_LOG_LEVEL=trace # takes precedence
# default log level is off - no logs from auth plugin
# [ "fatal", "error", "warn", "info", "debug", "trace", "all" ]
// on the client
import { edenTreaty } from '@elysiajs/eden'
import type { App } from '../../api/src/index'
const app = edenTreaty<App>('http://localhost:3000')
const login = async () => {
// get code from url (query param)
const response = await app['social-login'].post({
code: "<from query param>"
})
// type Response = { sessionToken: string }
// save sessionToken
// and then send it on all subsequent request
// in "authorization" header
}
And that's it.
Advanced setup
You might want to connect to multiple providers, or configure certain other aspects of the plugin. With the typed config it is pretty self-explanatory, here is a full example nonetheless.
// plugin/auth.ts
import { Static, t } from "elysia"
import { IdTokenPayload, multiProviderAuth as authPlugin } from "elysoid"
const SessionSchema = t.Object({
id: t.String(),
roles: t.Array(t.String())
}) // create any session schema that you need for your app
type Session = Static<typeof SessionSchema>
// type IdTokenPayload = { sub: string, email?: string, name?: string }
const login = async (payload: IdTokenPayload, provider: string): Promise<Session | null> => {
// Get user from DB, based on "sub" (or create)
// - BASED ON PROVIDER!!! ("google", "apple"... see below in the config)
// - with all required data for session
// - return null if not authorized to login
return {
id: "1",
roles: [ "admin" ]
}
}
export const auth = authPlugin(SessionSchema, login, {
session: {
authHeader: "authorization",
tokenPrefix: "Bearer ",
},
jwt: {
secret: "shhh",
expiration: "2h",
},
providers: {
'google': {
tokenEndpoint: "https://google.com/token",
clientId: "yourgoogleclientid",
clientSecret: "yourgoogleclientsecret",
redirectUri: "https://yourapp.com/googlecb"
},
'apple': {
tokenEndpoint: "https://apple.com/token",
clientId: "yourappleclientid",
clientSecret: "yourappleclientsecret",
redirectUri: "https://yourapp.com/applecb"
},
}
}, (logMessage) => console.log(logMessage))
// on the client
import { edenTreaty } from '@elysiajs/eden'
import type { App } from '../../api/src/index'
const app = edenTreaty<App>('http://localhost:3000')
const login = async () => {
// get code from url (query param)
const response = await app['social-login'].post({
code: "<from query param>",
provider: "google" // based on where were you redirected to
})
// type Response = { sessionToken: string }
// save sessionToken
// and then send it on all subsequent request
// in "authorization" header
}