Skip to content

Commit

Permalink
feat: Expo Auth without setting AUTH_URL. (#1054)
Browse files Browse the repository at this point in the history
* feat: expo-auth without auth_url env var

* Fix session cookie matching

* feat: Restore old CSRF checks in non-dev environments

* chore: Documenting some decisions with comments

* Use node env instead of vercel-specific env var

* Update readme to describe oauth changes

* Fix redirectTo being missing and enforce home nav since it was showing a weird page

* Disallow backwards navigation upon auth change
  • Loading branch information
Wundero authored Jun 9, 2024
1 parent 1c8655b commit a8deb69
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 7 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ The generator sets up the `package.json`, `tsconfig.json` and a `index.ts`, as w

### 4. Configuring Next-Auth to work with Expo

In order for the CSRF protection to work when developing locally, you will need to set the AUTH_URL to the same IP address your expo dev server is listening on. This address is displayed in your Expo CLI when starting the dev server.
In order to get Next-Auth to work with Expo, you must either:
- Add your local IP (e.g. 192.168.x.y) to your OAuth provider (by default Discord), and you may have to update this if it gets reassigned
- Deploy the Auth Proxy and point your OAuth provider to the proxy

## FAQ

Expand Down
6 changes: 6 additions & 0 deletions apps/expo/src/utils/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import * as Browser from "expo-web-browser";
import { api } from "./api";
import { getBaseUrl } from "./base-url";
import { deleteToken, setToken } from "./session-store";
import { useRouter } from "expo-router";

export const signIn = async () => {
const signInUrl = `${getBaseUrl()}/api/auth/signin`;
const redirectTo = Linking.createURL("/login");
const result = await Browser.openAuthSessionAsync(
`${signInUrl}?expo-redirect=${encodeURIComponent(redirectTo)}`,
redirectTo,
);

if (result.type !== "success") return;
Expand All @@ -27,21 +29,25 @@ export const useUser = () => {

export const useSignIn = () => {
const utils = api.useUtils();
const router = useRouter();

return async () => {
await signIn();
await utils.invalidate();
router.replace('/');
};
};

export const useSignOut = () => {
const utils = api.useUtils();
const signOut = api.auth.signOut.useMutation();
const router = useRouter();

return async () => {
const res = await signOut.mutateAsync();
if (!res.success) return;
await deleteToken();
await utils.invalidate();
router.replace('/');
};
};
34 changes: 28 additions & 6 deletions apps/nextjs/src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
import type { NextRequest } from "next/server";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { NextRequest, NextResponse } from "next/server";

import { GET as DEFAULT_GET, POST } from "@acme/auth";
import { GET as DEFAULT_GET, POST as DEFAULT_POST } from "@acme/auth";

import { env } from "~/env";

export const runtime = "edge";

const EXPO_COOKIE_NAME = "__acme-expo-redirect-state";
const AUTH_COOKIE_PATTERN = /authjs\.session-token=([^;]+)/;

/**
* Correct request.url for local development so that Expo can work. Does nothing in production.
* @param req The request to modify
* @returns The modified request.
*/
function rewriteRequestUrl(req: NextRequest) {
if (env.NODE_ENV === 'production') {
return req;
}
const host = req.headers.get("host");
const newURL = new URL(req.url);
newURL.host = host ?? req.nextUrl.host;
return new NextRequest(newURL, req);
}

export const POST = async (req: NextRequest) => {
// First step must be to correct the request URL.
req = rewriteRequestUrl(req);
return DEFAULT_POST(req);
};

export const GET = async (
req: NextRequest,
props: { params: { nextauth: string[] } },
) => {
// First step must be to correct the request URL.
req = rewriteRequestUrl(req);
const nextauthAction = props.params.nextauth[0];
const isExpoSignIn = req.nextUrl.searchParams.get("expo-redirect");
const isExpoCallback = cookies().get(EXPO_COOKIE_NAME);
Expand All @@ -34,7 +58,7 @@ export const GET = async (
const authResponse = await DEFAULT_GET(req);
const setCookie = authResponse.headers
.getSetCookie()
.find((cookie) => cookie.startsWith("authjs.session-token"));
.find((cookie) => AUTH_COOKIE_PATTERN.test(cookie));
const match = setCookie?.match(AUTH_COOKIE_PATTERN)?.[1];

if (!match)
Expand All @@ -51,5 +75,3 @@ export const GET = async (
// Every other request just calls the default handler
return DEFAULT_GET(req);
};

export { POST };
1 change: 1 addition & 0 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"dependencies": {
"@acme/db": "workspace:*",
"@auth/core": "0.31.0",
"@auth/drizzle-adapter": "^1.1.0",
"@t3-oss/env-nextjs": "^0.10.1",
"next": "^14.2.3",
Expand Down
8 changes: 8 additions & 0 deletions packages/auth/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type {
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import Discord from "next-auth/providers/discord";

import { skipCSRFCheck } from '@auth/core';

import { db } from "@acme/db/client";
import { Account, Session, User } from "@acme/db/schema";

Expand All @@ -23,8 +25,14 @@ const adapter = DrizzleAdapter(db, {
sessionsTable: Session,
});

// eslint-disable-next-line no-restricted-properties
const isSecureContext = process.env.NODE_ENV !== 'development';

export const authConfig = {
adapter,
// In development, we need to skip checks to allow Expo to work
skipCSRFCheck: isSecureContext ? undefined : skipCSRFCheck,
trustHost: !isSecureContext,
providers: [Discord],
callbacks: {
session: (opts) => {
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a8deb69

Please sign in to comment.