-
-
Notifications
You must be signed in to change notification settings - Fork 419
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: expo auth * rm * fix format * fix lock * feat: use expo-linking to construct urls for expo auth (#832) * expo install --fix * nit * fix cookie * use sync session store api * add custom handler back * fix exports * Use .find() instead of always first cookie (#1043) * feat: Expo Auth without setting AUTH_URL. (#1054) * 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 * some light refactoring * dont' mutate args * helper * cmt * fmt --------- Co-authored-by: Jay McMullen <[email protected]> Co-authored-by: Gabriel Bianchi <[email protected]> Co-authored-by: Sam Riddle <[email protected]>
- Loading branch information
1 parent
74c3c58
commit 390b1b1
Showing
16 changed files
with
316 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import * as Linking from "expo-linking"; | ||
import { useRouter } from "expo-router"; | ||
import * as Browser from "expo-web-browser"; | ||
|
||
import { api } from "./api"; | ||
import { getBaseUrl } from "./base-url"; | ||
import { deleteToken, setToken } from "./session-store"; | ||
|
||
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; | ||
const url = Linking.parse(result.url); | ||
const sessionToken = String(url.queryParams?.session_token); | ||
if (!sessionToken) return; | ||
|
||
setToken(sessionToken); | ||
}; | ||
|
||
export const useUser = () => { | ||
const { data: session } = api.auth.getSession.useQuery(); | ||
return session?.user ?? null; | ||
}; | ||
|
||
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("/"); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import Constants from "expo-constants"; | ||
|
||
/** | ||
* Extend this function when going to production by | ||
* setting the baseUrl to your production API URL. | ||
*/ | ||
export const getBaseUrl = () => { | ||
/** | ||
* Gets the IP address of your host-machine. If it cannot automatically find it, | ||
* you'll have to manually set it. NOTE: Port 3000 should work for most but confirm | ||
* you don't have anything else running on it, or you'd have to change it. | ||
* | ||
* **NOTE**: This is only for development. In production, you'll want to set the | ||
* baseUrl to your production API URL. | ||
*/ | ||
const debuggerHost = Constants.expoConfig?.hostUri; | ||
const localhost = debuggerHost?.split(":")[0]; | ||
|
||
if (!localhost) { | ||
// return "https://turbo.t3.gg"; | ||
throw new Error( | ||
"Failed to get localhost. Please point to your production server.", | ||
); | ||
} | ||
return `http://${localhost}:3000`; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import * as SecureStore from "expo-secure-store"; | ||
|
||
const key = "session_token"; | ||
export const getToken = () => SecureStore.getItem(key); | ||
export const deleteToken = () => SecureStore.deleteItemAsync(key); | ||
export const setToken = (v: string) => SecureStore.setItem(key, v); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,81 @@ | ||
export { GET, POST } from "@acme/auth"; | ||
import { cookies } from "next/headers"; | ||
import { NextRequest, NextResponse } from "next/server"; | ||
|
||
import { handlers, isSecureContext } from "@acme/auth"; | ||
|
||
export const runtime = "edge"; | ||
|
||
const EXPO_COOKIE_NAME = "__acme-expo-redirect-state"; | ||
const AUTH_COOKIE_PATTERN = /authjs\.session-token=([^;]+)/; | ||
|
||
/** | ||
* Noop in production. | ||
* | ||
* In development, rewrite the request URL to use localhost instead of host IP address | ||
* so that Expo Auth works without getting trapped by Next.js CSRF protection. | ||
* @param req The request to modify | ||
* @returns The modified request. | ||
*/ | ||
function rewriteRequestUrlInDevelopment(req: NextRequest) { | ||
if (isSecureContext) 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. | ||
const req = rewriteRequestUrlInDevelopment(_req); | ||
return handlers.POST(req); | ||
}; | ||
|
||
export const GET = async ( | ||
_req: NextRequest, | ||
props: { params: { nextauth: string[] } }, | ||
) => { | ||
// First step must be to correct the request URL. | ||
const req = rewriteRequestUrlInDevelopment(_req); | ||
|
||
const nextauthAction = props.params.nextauth[0]; | ||
const isExpoSignIn = req.nextUrl.searchParams.get("expo-redirect"); | ||
const isExpoCallback = cookies().get(EXPO_COOKIE_NAME); | ||
|
||
if (nextauthAction === "signin" && !!isExpoSignIn) { | ||
// set a cookie we can read in the callback | ||
// to know to send the user back to expo | ||
cookies().set({ | ||
name: EXPO_COOKIE_NAME, | ||
value: isExpoSignIn, | ||
maxAge: 60 * 10, // 10 min | ||
path: "/", | ||
}); | ||
} | ||
|
||
if (nextauthAction === "callback" && !!isExpoCallback) { | ||
cookies().delete(EXPO_COOKIE_NAME); | ||
|
||
// Run original handler, then extract the session token from the response | ||
// Send it back via a query param in the Expo deep link. The Expo app | ||
// will then get that and set it in the session storage. | ||
const authResponse = await handlers.GET(req); | ||
const setCookie = authResponse.headers | ||
.getSetCookie() | ||
.find((cookie) => AUTH_COOKIE_PATTERN.test(cookie)); | ||
const match = setCookie?.match(AUTH_COOKIE_PATTERN)?.[1]; | ||
|
||
if (!match) | ||
throw new Error( | ||
"Unable to find session cookie: " + | ||
JSON.stringify(authResponse.headers.getSetCookie()), | ||
); | ||
|
||
const url = new URL(isExpoCallback.value); | ||
url.searchParams.set("session_token", match); | ||
return NextResponse.redirect(url); | ||
} | ||
|
||
// Every other request just calls the default handler | ||
return handlers.GET(req); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.