Skip to content

Commit

Permalink
feat: initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
meza committed Feb 27, 2023
1 parent 956cc87 commit f80f3b8
Show file tree
Hide file tree
Showing 24 changed files with 12,977 additions and 2 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
30 changes: 30 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"extends": [
"tailored-tunes",
"plugin:json/recommended",
"plugin:security/recommended"
],
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": [
"json",
"@typescript-eslint"
],
"rules": {
"no-console": "off",
"no-else-return": "off",
"no-unused-expressions": "off",
"max-nested-callbacks": ["error", 4]
},
"env": {
"commonjs": false,
"es6": true,
"node": true
},
"ignorePatterns": [
"**/*.d.ts"
]
}
53 changes: 53 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"enabledManagers": [
"npm"
],
"baseBranches": [
"main"
],
"semanticCommits": "enabled",
"semanticCommitType": "chore",
"packageRules": [
{
"matchDepTypes": [
"devDependencies"
],
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true,
"semanticCommitType": "chore"
},
{
"matchDepTypes": [
"dependencies"
],
"matchUpdateTypes": [
"patch"
],
"automerge": true,
"semanticCommitType": "chore"
},
{
"matchDepTypes": [
"dependencies"
],
"matchUpdateTypes": [
"minor"
],
"automerge": true,
"semanticCommitType": "fix"
}
],
"schedule": [
"after 10pm and before 5am every weekday",
"every weekend"
]
}
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Verify

on:
push:
branches: [ main, alpha, beta ]

env:
LEFTHOOK: 0

jobs:
verify:
uses: meza/shared-github-workflows/.github/workflows/default-node-npm-ci.yml@main
secrets:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
with:
test: false
node-version: "latest"
junit-report-path: "reports/junit.xml"
cobertura-report-path: "reports/coverage/cobertura-coverage.xml"
build:
runs-on: ubuntu-latest
needs: [ verify ]
if: needs.verify.outputs.new-release-published == 'true'
name: Build
steps:
- name: ⏬ Checkout
uses: actions/checkout@v3
- name: 🔧 Set up node
uses: meza/action-setup-node-npm@main
with:
node-version: latest
cache-name: ${{ needs.verify.outputs.cache-name }}
- name: 🔢 Set version
run: npm version --no-git-tag-version ${{ needs.verify.outputs.new-release-version }}
- name: 🔨 Build
run: npm run build
- name: 🚀 Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm run release
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.idea
dist
node_modules
reports
.cache

24 changes: 24 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"branches": [
"+([0-9])?(.{+([0-9]),x}).x",
"main",
"next",
{
"name": "beta",
"prerelease": true
},
{
"name": "alpha",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"semantic-release-export-data",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
[
"@semantic-release/github"
]
]
}
188 changes: 186 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,186 @@
# auth0-remix-server
An Auth0 library for server side Remix
<div style="text-align: center;">
<h1>Auth0 Remix Server</h1>
<p>
The missing library for authentication on the server with Remix
</p>
<p>
<i>Please contribute!</i>
</p>
</div>

---

## What is this?

This is a library for authentication with [Auth0](https://auth0.com/) on the server with Remix.

It's built as part of the efforts to deliver the [trance-stack](https://github.com/meza/trance-stack/). As such, the initial
release of this library only covers the MVP needs of the stack. It will keep evolving over time and with your help.

## What is still missing?

- [ ] utilise the STATE parameter to prevent CSRF
- [ ] failed events should remove the user from the session automatically
- [ ] see if we can handle the callback while maintaining the session from before the login
- [ ] create the callbacks for the id token and the refresh tokens
- [ ] opt out of the session handling
- [ ] enable register with passing ?screen_hint=signup to the authorize endpoint

## How to use

Everything below assumes that you're using [Remix](https://remix.run/) and [Auth0](https://auth0.com/) and you're familiar
with how they work.

### Installation

```bash
npm install @meza/auth0-remix-server
```

### Usage

Some steps below might be familiar to anyone who attempted this with the [remix-auth-auth0](https://github.com/danestves/remix-auth-auth0/) package.

> #### Environment Variables
> Environment variables are not required for the library, the examples only use them when configuring the authenticator.
> I do recommend using environment variables for sensitive information like client secrets and domain names.
>
> `AUTH0_DOMAIN` - The domain name of your Auth0 tenant
> `AUTH0_CLIENT_ID` - The client ID of your Auth0 application
> `AUTH0_CLIENT_SECRET` - The client secret of your Auth0 application
> `APP_DOMAIN` - The domain name of your application (http://localhost:3333 for local development)
#### 1. Create an instance of the authenticator in `src/auth.server.ts`

```ts
// src/auth.server.ts
import { Auth0RemixServer } from '@meza/auth0-remix-server';
import { getSessionStorage } from './sessionStorage.server'; // this is where your session storage is configured

export const authenticator = new Auth0RemixServer({
clientDetails: {
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET
},
callbackURL: `${process.env.APP_DOMAIN}/auth/callback`,
refreshTokenRotationEnabled: true,
failedLoginRedirect: '/',
session: {
store: getSessionStorage()
}
});
```

#### 2. Create a login route in `src/routes/login.tsx`

```tsx
// src/routes/login.tsx
import { redirect } from '@remix-run/node';

export default () => {
return (
<Form action="/auth/auth0" method="post">
<button>Login</button>
</Form>
);
};
```

#### 3. Create an authentication route in `src/routes/auth/auth0.ts`

```tsx
// src/routes/auth/auth0.ts
import { authenticator } from '../../auth.server';
import type { ActionFunction } from '@remix-run/node';

export const action: ActionFunction = () => {
const forceLogin = false; // set to true to force auth0 to ask for a login
authenticator.authorize(forceLogin);
};
```

#### 4. Create a callback route in `src/routes/auth/callback.tsx`

```tsx
// src/routes/auth/callback.tsx
import { authenticator } from '../../auth.server';
import type { ActionFunction } from '@remix-run/node';

export const action: ActionFunction = async ({ request }) => {
await authenticator.handleCallback(request, {
onSuccessRedirect: '/dashboard' // change this to be wherever you want to redirect to after a successful login
});
};
```

#### 5. Create a logout route in `src/routes/logout.tsx`

```tsx
import { authenticator } from '../auth.server';
import { destroySession, getSessionFromRequest } from '../session.server';
import type { ActionFunction } from '@remix-run/node';

export const action: ActionFunction = async ({ request }) => {
const session = await getSessionFromRequest(request);

await authenticator.logout(process.env.APP_DOMAIN, {
'Set-Cookie': await destroySession(session) // this is where you destroy the session
});
};

export const loader = action; // this to allow you to hit /logout directly in the browser
```

#### 6. Optional - Create a dashboard route in `src/routes/dashboard.tsx`

```tsx
import { json } from '@remix-run/node';
import { Form, useLoaderData } from '@remix-run/react';
import { authenticator } from '../auth.server';
import type { LoaderFunction } from '@remix-run/node';

export const loader: LoaderFunction = async ({ request, context }) => {
const user = await authenticator.getUser(request, context); // this is what determines if the user is logged in or not
return json({
user: user
});
};

export default () => {
const { user } = useLoaderData<typeof loader>();
return (
<div>
<div>Dashboard for {user.nickname || user.givenName || user.name}</div>
<Form action="/logout" method="post">
<button>Logout</button>
</Form>
</div>
);
};


```

## Gotchas

### Refresh Token Rotation

The `refreshTokenRotationEnabled` option is set to `false` by default. This is because it's off by default in Auth0.

When it's set to `true`, the refresh tokens will be appended to the session. This is secure and makes it easier to manage
the refresh tokens.

Please see [this post](https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation) and [this one](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/#Refresh-Token-Rotation)
for more information.

### Refreshing the access token

Until [this issue](https://github.com/remix-run/react-router/issues/9566) in Remix is shipped, you'll need to pass in
the context from the loaders and actions to the `getUser` method.

This ensures (in an awkward way) that the refresh only happens once.

It's not pretty but once we have proper middleware in Remix, it should clean up.


28 changes: 28 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Lefthook
#
# Skip lefthook execution:
# e.g. `LEFTHOOK=0 git commit -am "Lefthook skipped"`
# https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md#skip-lefthook-execution
#
#
# Full Lefthook guide:
# https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md
#
# Full list of git hooks:
# https://git-scm.com/docs/githooks

commit-msg:
commands:
lint-commit-msg:
run: npx commitlint --edit
pre-commit:
parallel: true
commands:
lint:
run: npm run lint
test:
run: npm run report
post-merge:
commands:
install-deps-postmerge:
run: npm install
Loading

0 comments on commit f80f3b8

Please sign in to comment.