Skip to content

Commit 94bf31e

Browse files
authored
feat: make useSession isomorph, improve docs credential flow example, add docs security section, add docs custom sign in page section (#31)
* feat: make useSession isomorph :confetti: * feat(docs): more realistic credentials flow * polish: security section in docs, rename some useSession internals for more consistency * docs: add isomorphic auth to docs * Revert "docs: add isomorphic auth to docs" This reverts commit 4cfaa3e. * docs: add isomorphic auth to docs * feat: switch to approach that avoids double-async nested composables (see https://github.com/nuxt/framework/issues/5740\#issuecomment-1229197529) * polish: remove bad redirect fallback, fix typing * polish: use FetchOptions directly, add ofetch as dev-dep * docs: add section on custom signin page
1 parent 37cd259 commit 94bf31e

File tree

7 files changed

+254
-110
lines changed

7 files changed

+254
-110
lines changed

README.md

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ There's more supported methods in the `useSession` composable, you can create [u
7272
- ✔️ Custom OAuth (write it yourself)
7373
- ✔️ Credentials (password + username)
7474
- 🚧 Email Magic URLs
75-
- ✔️ Client Library:
75+
- ✔️ Isomorphic / Universal Auth Composable:
7676
- `useSession` composable to: `signIn`, `signOut`, `getCsrfToken`, `getProviders`, `getSession`
7777
- full typescript support for all methods and property
7878
- ✔️ Persistent sessions across requests
@@ -117,6 +117,7 @@ Below we describe:
117117
2. [Application-side usage](#application-side-usage)
118118
- [Session access and manipulation](#session-access-and-manipulation)
119119
- [Redirects](#redirects)
120+
- [Custom sign in page](#custom-sign-in-page)
120121
- [Middleware](#middleware)
121122
- [Global middleware](#global-middleware)
122123
- [Named middleware](#named-middleware)
@@ -127,11 +128,13 @@ Below we describe:
127128
- [Getting the JWT token](#getting-the-jwt-token)
128129
- [Application-side JWT token access](#application-side-jwt-token-access)
129130
4. [REST API](#rest-api)
130-
5. [Glossary](#glossary)
131-
6. [Prior Work and Module Concept](#prior-work-and-module-concept)
131+
5. [Security](#security)
132+
- [Disclosure](#disclosure)
133+
6. [Glossary](#glossary)
134+
7. [Prior Work and Module Concept](#prior-work-and-module-concept)
132135
- [Project Roadmap](#project-roadmap)
133-
7. [Module Playground](#module-playground)
134-
8. [Development](#development)
136+
8. [Module Playground](#module-playground)
137+
9. [Development](#development)
135138
136139
### Configuration
137140
@@ -216,9 +219,9 @@ export default NuxtAuthHandler({
216219
providers: [
217220
// @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point
218221
GithubProvider.default({
219-
clientId: 'a-client-id',
220-
clientSecret: 'a-client-secret'
221-
})
222+
clientId: 'your-client-id',
223+
clientSecret: 'your-client-secret'
224+
}),
222225
// @ts-ignore Import is exported on .default during SSR, so we need to call it this way. May be fixed via Vite at some point
223226
CredentialsProvider.default({
224227
// The name to display on the sign in form (e.g. 'Sign in with...')
@@ -228,24 +231,24 @@ export default NuxtAuthHandler({
228231
// e.g. domain, username, password, 2FA token, etc.
229232
// You can pass any HTML attribute to the <input> tag through the object.
230233
credentials: {
231-
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
232-
password: { label: 'Password', type: 'password' }
234+
username: { label: 'Username', type: 'text', placeholder: '(hint: jsmith)' },
235+
password: { label: 'Password', type: 'password', placeholder: '(hint: hunter2)' }
233236
},
234237
authorize (credentials: any) {
235238
// You need to provide your own logic here that takes the credentials
236239
// submitted and returns either a object representing a user or value
237240
// that is false/null if the credentials are invalid.
238-
// e.g. return { id: 1, name: 'J Smith', email: '[email protected]' }
239-
// You can also use the `req` object to obtain additional parameters
240-
// (i.e., the request IP address)
241-
// eslint-disable-next-line no-console
242-
console.log('provided credentials: ', credentials)
243-
const user = { id: '1', name: 'J Smith', email: '[email protected]' }
244-
245-
if (user) {
241+
// NOTE: THE BELOW LOGIC IS NOT SAFE OR PROPER FOR AUTHENTICATION!
242+
243+
const user = { id: '1', name: 'J Smith', username: 'jsmith', password: 'hunter2' }
244+
245+
if (credentials?.username === user.username && credentials?.password === user.password) {
246246
// Any object returned will be saved in `user` property of the JWT
247247
return user
248248
} else {
249+
// eslint-disable-next-line no-console
250+
console.error('Warning: Malicious login attempt registered, bad credentials provided')
251+
249252
// If you return null then an error will be displayed advising the user to check their details.
250253
return null
251254
@@ -255,7 +258,6 @@ export default NuxtAuthHandler({
255258
})
256259
]
257260
})
258-
259261
```
260262
261263
Note that there's way more options inside the `nextAuth.options` object, see [here](https://next-auth.js.org/configuration/options#options) for all available options.
@@ -308,6 +310,9 @@ await signIn(undefined, { callbackUrl: '/protected' })
308310
// Trigger a sign in via a specific authentication provider with a redirect afterwards, see https://next-auth.js.org/getting-started/client#signin
309311
await signIn('github', { callbackUrl: '/protected' })
310312
313+
// Trigger a sign in with username and password already passed, e.g., from your own custom-made sign-in form
314+
await singIn('credentials', { username: 'jsmith', password: 'hunter2' })
315+
311316
// Trigger a sign out, see https://next-auth.js.org/getting-started/client#signout
312317
await signOut()
313318
```
@@ -344,6 +349,28 @@ await signOut({ callbackUrl: '/protected' })
344349
345350
E.g., here to redirect the user away from the already loaded, protected, page after signout (else, you will have to handle the redirect yourself).
346351
352+
##### Custom sign in page
353+
354+
To create your custom sign-in page you can use `signIn` to directly start a provider-flow once the user selected it, e.g., by clicking on a button on your custom sign-in page. Here is a very simple sign-in page that either directly starts a github-oauth sign in flow or directly signs in the user via the credentials flow:
355+
```vue
356+
<template>
357+
<div>
358+
<p>Sign In Options</p>
359+
<button @click="signIn('github')">Github</button>
360+
<!-- NOTE: Here we hard-coded username and password, on your own page this should probably be connected to two inputs for username + password -->
361+
<button @click="signIn('credentials', { username: 'test', password: 'hunter2' })">Username and Password</button>
362+
</div>
363+
</template>
364+
365+
<script setup lang="ts">
366+
const { signIn } = await useSession({ required: false })
367+
</script>
368+
```
369+
370+
Note: In the above example `username` and `password` are hard-coded. In your own custom page, these two fields should probably come from inputs on your page.
371+
372+
If you want to create a custom sign-in page that dynamically offers sign-in options based on your configured providers, you can call `getProviders()` first and then iterate over the supported providers to generate your sign in page.
373+
347374
#### Middleware
348375
349376
You can use this library to define application middleware. This library supports all of [Nuxt's supported approaches](https://v3.nuxtjs.org/guide/directory-structure/middleware#middleware-directory), read on to learn how.
@@ -542,6 +569,24 @@ All endpoints that NextAuth.js supports are also supported by `nuxt-auth`:
542569
543570
You can directly interact with them if you wish to, it's probably a better idea to use `useSession` where possible though. [See the full rest API documentation of NextAuth.js here](https://next-auth.js.org/getting-started/rest-api).
544571
572+
#### Security
573+
574+
This section mostly contains a list of possible security problems. Note that the below flaws exist with many libraries and frameworks we use in our day-to-day when building and working with APIs. Even your vanilla Nuxt app already posesses some of these shortcoming. Missing in the below list are estimates of how likely it is that one of the list-items may occur and what impact it will have on your app. This is because that heavily depends on:
575+
- your app: Are you building a fun project? A proof of concept? The next fort-nox money management app?
576+
- your environment: Building a freely available app for fun? Have authentication in front of your app and trust all users that successfully authenticated? Superb! Don't trust anyone? Then please be extra-careful when using this library and when building you backend in general
577+
578+
Without further ado, here's some attack cases you can consider and take action against. Neither the attack vectors, the problems or the mitigations are exhaustive:
579+
1. sending arbitrary data: Denial-of-Service by server-ressource exhaustion (bandwidth, cpu, memory), arbitrary code execution (if you parse the data), ...
580+
2. creation arbitrarily many sessions: Denial-of-Service by server-ressource exhaustion (bandwidth, cpu, memory)
581+
3. guessing correct session ids: session data can leak
582+
4. stealing session id(s) of client(s): session data can leak
583+
584+
Read up how to mitigate these and more issues if you see fit. Checkout the [`nuxt-security`](https://github.com/Baroshem/nuxt-security) module that may help with some of these.
585+
586+
##### Disclosure
587+
588+
A last reminder: This library was not written by crypto- or security-experts. Please proceed at your own risk, inspect the code if you want to and open issues / pull requests where you see room for improvement. If you want to file a security-concern privately, please send an email to `[email protected]` with the subject saying "SECURITY nuxt-auth" and we'll look into your request ASAP.
589+
545590
#### Glossary
546591
547592
There are some terms we use in this documentation that may not immeadiatly be known to every reader. Here is an explanation for some of them:

package-lock.json

Lines changed: 59 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
"dependencies": {
2929
"@nuxt/kit": "^3.0.0-rc.12",
3030
"defu": "^6.1.0",
31-
"nanoid": "^4.0.0",
3231
"next-auth": "^4.14.0",
3332
"ufo": "^0.8.6"
3433
},
@@ -37,6 +36,7 @@
3736
"@nuxt/schema": "^3.0.0-rc.12",
3837
"@nuxtjs/eslint-config-typescript": "^11.0.0",
3938
"eslint": "^8.25.0",
40-
"nuxt": "^3.0.0-rc.12"
39+
"nuxt": "^3.0.0-rc.12",
40+
"ofetch": "^1.0.0"
4141
}
4242
}

playground/pages/index.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<template>
22
<div>
3-
<button @click="signIn(undefined, { callbackUrl: '/'})">
3+
<button @click="signIn(undefined, { callbackUrl: '/' })">
44
sign in
55
</button>
66
<br>
7-
<button @click="signIn('credentials', { callbackUrl: '/'})">
7+
<button @click="signIn('credentials', { callbackUrl: '/', username: 'jsmith', password: 'hunter2' })">
88
sign in (credential)
99
</button>
1010
<br>
11-
<button @click="signIn('github', { callbackUrl: '/'})">
11+
<button @click="signIn('github', { callbackUrl: '/' })">
1212
sign in (github)
1313
</button>
1414
<br>
@@ -20,7 +20,7 @@
2020
sign out
2121
</button>
2222
<br>
23-
<button @click="getSession()">
23+
<button @click="getSession({ required: false })">
2424
refresh session
2525
</button>
2626
</div>

playground/server/api/auth/[...].ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,24 @@ export default NuxtAuthHandler({
1818
// e.g. domain, username, password, 2FA token, etc.
1919
// You can pass any HTML attribute to the <input> tag through the object.
2020
credentials: {
21-
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
22-
password: { label: 'Password', type: 'password' }
21+
username: { label: 'Username', type: 'text', placeholder: '(hint: jsmith)' },
22+
password: { label: 'Password', type: 'password', placeholder: '(hint: hunter2)' }
2323
},
2424
authorize (credentials: any) {
2525
// You need to provide your own logic here that takes the credentials
2626
// submitted and returns either a object representing a user or value
2727
// that is false/null if the credentials are invalid.
28-
// e.g. return { id: 1, name: 'J Smith', email: 'jsmith@example.com' }
29-
// You can also use the `req` object to obtain additional parameters
30-
// (i.e., the request IP address)
31-
// eslint-disable-next-line no-console
32-
console.log('provided credentials: ', credentials)
33-
const user = { id: '1', name: 'J Smith', email: '[email protected]' }
28+
// NOTE: THE BELOW LOGIC IS NOT SAFE OR PROPER FOR AUTHENTICATION!
3429

35-
if (user) {
30+
const user = { id: '1', name: 'J Smith', username: 'jsmith', password: 'hunter2' }
31+
32+
if (credentials?.username === user.username && credentials?.password === user.password) {
3633
// Any object returned will be saved in `user` property of the JWT
3734
return user
3835
} else {
36+
// eslint-disable-next-line no-console
37+
console.error('Warning: Malicious login attempt registered, bad credentials provided')
38+
3939
// If you return null then an error will be displayed advising the user to check their details.
4040
return null
4141

0 commit comments

Comments
 (0)