Skip to content

Commit

Permalink
feat(auth-server): Added JWT create function and updated readme
Browse files Browse the repository at this point in the history
  • Loading branch information
AleksandarDev committed Oct 10, 2024
1 parent 9f867c5 commit a9d249c
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 3 deletions.
65 changes: 65 additions & 0 deletions web/packages/auth-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# @signalco/auth-server

## Getting started

Initialize and expose the `withAuth` function that you can later use to protect your endpoints.

```ts
import { InitAuth } from '@signalco/auth-server';

function jwtSecret() {
const signSecret = process.env.MYAPP_JWT_SIGN_SECRET;
return new TextEncoder().encode(signSecret);
}

function getUser(id: string) {
return db().get<User>(id);
}

export const { withAuth, createJwt } = InitAuth({
namespace: 'myapp',
cookieName: 'myapp_session',
jwtSecretFactory: async () => jwtSecret(),
getUser
});
```

JWT sign secret should be a 256 bit (32 byte) secret key. You can generate one with `openssl rand -base64 32`. Keep it secret and safe. It should never be exposed to the client or stored in the repository. In example above, it is read from an environment variable.

Tip: If you somehow expose the sign secret, you can rotate it by changing the environment variable and restarting the server. This will invalidate all existing tokens and force users to log in again.

If you want to use a different cookie name, make sure to update the `cookieName` parameter.

Protecting endpoint with `withAuth`:

```ts
import { withAuth } from './auth';

app.get('/api/users/current', async (req, res) => {
const { user } = await withAuth();
res.json(req.user);
});
```

## Login

```ts
import { createJwt, setJwtCookie } from './auth';

// ... login logic (validate user credentials, etc.)

setJwtCookie(createJwt(user.id));

// or set cookie manually

const jwt = await createJwt(user.id);
cookies().set(
'myapp_session', // Make sure this matches configured `cookieName`
jwt,
{
secure: true,
httpOnly: true,
sameSite: 'strict',
expires: new Date(Date.now() + 1000 * 60 * 60 * 2),
});
```
2 changes: 1 addition & 1 deletion web/packages/auth-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@signalco/auth-server",
"version": "0.1.0",
"version": "0.1.1",
"sideEffects": false,
"type": "module",
"license": "MIT",
Expand Down
1 change: 0 additions & 1 deletion web/packages/auth-server/src/AuthConfigInitialized.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { UserBase } from './withAuth';
import { AuthConfig } from './AuthConfig';


export type AuthConfigInitialized<TUser extends UserBase> = Required<AuthConfig<TUser>>;
38 changes: 37 additions & 1 deletion web/packages/auth-server/src/InitAuth.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { cookies } from 'next/headers';
import { SignJWT } from 'jose';
import { UserBase, WithAuthContext, withAuth } from './withAuth';
import { AuthConfig } from './AuthConfig';

Expand All @@ -10,11 +12,45 @@ const defaultConfig = {
getUser: async () => { throw new Error('Not implemented'); }
};

async function createJwt(
userId: string,
namespace: string,
issuer: string,
audience: string,
expirationTime: string | number | Date,
jwtSecret: Uint8Array) {
return await new SignJWT()
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setIssuer(`urn:${namespace}:issuer:${issuer}`)
.setAudience(`urn:${namespace}:audience:${audience}`)
.setExpirationTime(expirationTime)
.setSubject(userId)
.sign(jwtSecret);
}

export function InitAuth<TUser extends UserBase>(config: AuthConfig<TUser>): {
withAuth: (handler: (ctx: WithAuthContext<TUser>) => Promise<Response>) => Promise<Response>;
createJwt: (userId: string, expirationTime?: string | number | Date) => Promise<string>;
setJwtCookie: (jwt: Promise<string> | string) => Promise<void>;
} {
const initializedConfig = { ...defaultConfig, ...config };
return {
withAuth: (handler) => withAuth(initializedConfig, handler)
withAuth: (handler) => withAuth(initializedConfig, handler),
createJwt: async (userId: string, expirationTime: string | number | Date = '1h') => await createJwt(
userId,
initializedConfig.namespace,
initializedConfig.issuer,
initializedConfig.audience,
expirationTime,
await initializedConfig.jwtSecretFactory()),
setJwtCookie: async (jwt) => {
cookies().set(initializedConfig.cookieName, await Promise.resolve(jwt), {
secure: true,
httpOnly: true,
sameSite: 'strict',
expires: new Date(Date.now() + 1000 * 60 * 60 * 2),
});
}
};
}

0 comments on commit a9d249c

Please sign in to comment.