Skip to content

Commit

Permalink
feat: add logout api route (#32)
Browse files Browse the repository at this point in the history
* feat: add logout api route

* chore: update changelog.

* feat: logout returnTo in HandleAuthOptions.
  • Loading branch information
thorwebdev authored Mar 3, 2022
1 parent 4a980a2 commit ad3c71a
Show file tree
Hide file tree
Showing 12 changed files with 84 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 1.2.0 - 2022-03-01

- [BREAKING CHANGE][#32](https://github.com/supabase-community/supabase-auth-helpers/pull/32): feat: add logout api route. Note that this includes a breaking change to the options parameter for `handleAuth(options: HandleAuthOptions)` See [the docs](./src/nextjs/README.md#basic-setup) for more details.

## 1.1.3 - 2022-02-23

- [#30](https://github.com/supabase-community/supabase-auth-helpers/pull/30): fix: Unknown encoding: base64url.
Expand Down
1 change: 1 addition & 0 deletions examples/nextjs/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { supabaseClient } from '@supabase/supabase-auth-helpers/nextjs';
function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider supabaseClient={supabaseClient}>
<a href="/api/auth/logout">Logout</a>
<Component {...pageProps} />
</UserProvider>
);
Expand Down
2 changes: 1 addition & 1 deletion examples/nextjs/pages/api/auth/[...supabase].ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { handleAuth } from '@supabase/supabase-auth-helpers/nextjs';

export default handleAuth();
export default handleAuth({ logout: { returnTo: '/' } });
1 change: 0 additions & 1 deletion examples/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const LoginPage: NextPage = () => {
[<Link href="/profile">withAuthRequired</Link>] | [
<Link href="/protected-page">supabaseServerClient</Link>]
</p>
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
{isLoading ? <h1>Loading...</h1> : <h1>Loaded!</h1>}
<p>user:</p>
<pre>{JSON.stringify(user, null, 2)}</pre>
Expand Down
4 changes: 3 additions & 1 deletion src/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The path to your dynamic API route file would be `/pages/api/auth/[...supabase].
```js
import { handleAuth } from '@supabase/supabase-auth-helpers/nextjs';

export default handleAuth();
export default handleAuth({ logout: { returnTo: '/' } });
```

Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow:
Expand All @@ -48,6 +48,8 @@ Executing `handleAuth()` creates the following route handlers under the hood tha

- `/api/auth/user`: You can fetch user profile information in JSON format.

- `/api/auth/logout`: Your Next.js application logs out the user. You can optionally pass a `returnTo` parameter to return to a custom relative URL after logout, eg `/api/auth/logout?returnTo=/login`. This will overwrite the logout `returnTo` option specified `handleAuth()`

Wrap your `pages/_app.js` component with the `UserProvider` component:

```jsx
Expand Down
22 changes: 16 additions & 6 deletions src/nextjs/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import { CookieOptions } from '../types';
import type { NextApiRequest, NextApiResponse } from 'next';
import handelCallback from './callback';
import handleUser from './user';
import { COOKIE_OPTIONS } from '../utils/constants';
import handleLogout from './logout';
import { COOKIE_OPTIONS } from '../../shared/utils/constants';

export default function handleAuth(
cookieOptions: CookieOptions = COOKIE_OPTIONS
) {
export interface HandleAuthOptions {
cookieOptions?: CookieOptions;
logout?: { returnTo?: string };
}

export default function handleAuth(options: HandleAuthOptions = {}) {
return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
const { cookieOptions = COOKIE_OPTIONS, logout } = options;
let {
query: { supabase: route }
} = req;
Expand All @@ -16,9 +21,14 @@ export default function handleAuth(

switch (route) {
case 'callback':
return handelCallback(req, res, cookieOptions);
return handelCallback(req, res, { cookieOptions });
case 'user':
return await handleUser(req, res, cookieOptions);
return await handleUser(req, res, { cookieOptions });
case 'logout':
return handleLogout(req, res, {
cookieOptions,
...logout
});
default:
res.status(404).end();
}
Expand Down
8 changes: 7 additions & 1 deletion src/nextjs/handlers/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import {
NextRequestAdapter,
NextResponseAdapter
} from '../../shared/adapters/NextAdapter';
import { COOKIE_OPTIONS } from '../../shared/utils/constants';

export interface HandleCallbackOptions {
cookieOptions?: CookieOptions;
}

export default function handelCallback(
req: NextApiRequest,
res: NextApiResponse,
cookieOptions: CookieOptions
options: HandleCallbackOptions = {}
) {
if (req.method !== 'POST') {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
const { cookieOptions = COOKIE_OPTIONS } = options;
const { event, session } = req.body;

if (!event) throw new Error('Auth event missing!');
Expand Down
43 changes: 43 additions & 0 deletions src/nextjs/handlers/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { CookieOptions } from '../types';
import type { NextApiRequest, NextApiResponse } from 'next';
import { setCookies } from '../../shared/utils/cookies';
import {
NextRequestAdapter,
NextResponseAdapter
} from '../../shared/adapters/NextAdapter';
import { supabaseClient } from '../utils/initSupabase';
import { COOKIE_OPTIONS } from '../../shared/utils/constants';

export interface HandleLogoutOptions {
cookieOptions?: CookieOptions;
returnTo?: string;
}

export default function handleLogout(
req: NextApiRequest,
res: NextApiResponse,
options: HandleLogoutOptions = {}
) {
let { returnTo } = req.query;
if (!returnTo) returnTo = options?.returnTo ?? '/';
returnTo = Array.isArray(returnTo) ? returnTo[0] : returnTo;
returnTo = returnTo.charAt(0) === '/' ? returnTo : `/${returnTo}`;
const { cookieOptions = COOKIE_OPTIONS } = options;

// Logout request to Gotrue
const access_token = req.cookies[`${cookieOptions.name}-access-token`];
if (access_token) supabaseClient.auth.api.signOut(access_token);

// Delete cookies
setCookies(
new NextRequestAdapter(req),
new NextResponseAdapter(res),
['access-token', 'refresh-token'].map((key) => ({
name: `${cookieOptions.name}-${key}`,
value: '',
maxAge: -1
}))
);

res.redirect(returnTo as string);
}
8 changes: 7 additions & 1 deletion src/nextjs/handlers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ import { ApiError, CookieOptions } from '../types';
import type { NextApiRequest, NextApiResponse } from 'next';
import { jwtDecoder } from '../../shared/utils/jwt';
import getUser from '../utils/getUser';
import { COOKIE_OPTIONS } from '../../shared/utils/constants';

export interface HandleUserOptions {
cookieOptions?: CookieOptions;
}

export default async function handleUser(
req: NextApiRequest,
res: NextApiResponse,
cookieOptions: CookieOptions
options: HandleUserOptions = {}
) {
try {
if (!req.cookies) {
throw new Error('Not able to parse cookies!');
}
const { cookieOptions = COOKIE_OPTIONS } = options;
const access_token = req.cookies[`${cookieOptions.name}-access-token`];

if (!access_token) {
Expand Down
2 changes: 1 addition & 1 deletion src/nextjs/utils/getUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import { User, createClient } from '@supabase/supabase-js';
import { CookieOptions } from '../types';
import { setCookies } from '../../shared/utils/cookies';
import { COOKIE_OPTIONS } from './constants';
import { COOKIE_OPTIONS } from '../../shared/utils/constants';
import { jwtDecoder } from '../../shared/utils/jwt';
import {
NextRequestAdapter,
Expand Down
2 changes: 1 addition & 1 deletion src/nextjs/utils/withAuthRequired.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from 'next';
import { jwtDecoder } from '../../shared/utils/jwt';
import { CookieOptions } from '../types';
import { COOKIE_OPTIONS } from './constants';
import { COOKIE_OPTIONS } from '../../shared/utils/constants';
import getAccessToken from './getAccessToken';
import getUser from './getUser';

Expand Down
File renamed without changes.

0 comments on commit ad3c71a

Please sign in to comment.