Skip to content

Commit

Permalink
FR-18273 - Support cookie domain (#369)
Browse files Browse the repository at this point in the history
  • Loading branch information
frontegg-david authored Oct 22, 2024
1 parent d9ae473 commit 6701674
Show file tree
Hide file tree
Showing 25 changed files with 363 additions and 64 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ jobs:
- run: yarn clean
- run: yarn install
- run: yarn build
- run: sudo apt-get update
- name: Install dependencies
run: |
sudo apt-get update
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run Playwright tests
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16.15.1
v18.12.1
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,14 @@ FRONTEGG_CLIENT_SECRET='{YOUR_APPLICATION_CLIENT_SECRET}'
# node -e "console.log(crypto.randomBytes(32).toString('hex'))"
FRONTEGG_ENCRYPTION_PASSWORD='{SESSION_ENCRYPTION_PASSWORD}'

# The stateless session cookie name
FRONTEGG_COOKIE_NAME='fe_session'
# The stateless cookie name for storing the encrypted JWT
# value as session cookies for supporting getServerSideProps and ServerComponents
FRONTEGG_COOKIE_NAME='FRONTEGG_COOKIE_NAME'

# Specifies the domain for storing the encrypted JWT as session cookies,
# enabling support for `getServerSideProps` and `ServerComponents`.
# If not provided, the domain will be the same as the `APP_URL` environment variable.
FRONTEGG_COOKIE_DOMAIN='FRONTEGG_COOKIE_DOMAIN'

# The JWT public key generated by Frontegg to verify JWT before creating a session.
# Retrieve it by visiting: https://[YOUR_FRONTEGG_DOMAIN]/.well-known/jwks.json.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@babel/register": "^7.21.0",
"@playwright/test": "^1.31.2",
"@playwright/test": "^1.48.1",
"@types/jest": "^29.5.0",
"@types/node": "^16.18.16",
"@types/react": "^18.0.28",
Expand Down
33 changes: 32 additions & 1 deletion packages/example-app-directory/app/UserState.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
'use client';
import { useAuthUserOrNull, useLoginWithRedirect, AdminPortal, useLogoutHostedLogin } from '@frontegg/nextjs';
import {
useAuthActions,
useAuthUserOrNull,
useLoginWithRedirect,
AdminPortal,
useLogoutHostedLogin,
} from '@frontegg/nextjs';
import Link from 'next/link';

export const UserState = () => {
const user = useAuthUserOrNull();
const loginWithRedirect = useLoginWithRedirect();
const logoutHosted = useLogoutHostedLogin();
const { switchTenant } = useAuthActions();

/*
* Replace the elements below with your own.
Expand All @@ -18,6 +25,30 @@ export const UserState = () => {

<br />
<br />
<div>
<h2>Tenants:</h2>
{(user?.tenants ?? []).map((tenant) => {
return (
<div key={tenant.tenantId}>
<b>Tenant Id:</b>
{tenant['tenantId']}
<span style={{ display: 'inline-block', width: '20px' }} />
{tenant.tenantId === user?.tenantId ? (
<b>Current Tenant</b>
) : (
<button
onClick={() => {
switchTenant({ tenantId: tenant.tenantId });
}}
>
Switch to tenant
</button>
)}
</div>
);
})}
</div>
<br />
<button
onClick={() => {
AdminPortal.show();
Expand Down
6 changes: 1 addition & 5 deletions packages/example-app-directory/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<head></head>
<body>
{/* @ts-expect-error Server Component for more details visit: https://github.com/vercel/next.js/issues/42292 */}
<FronteggAppProvider
authOptions={{ keepSessionAlive: true }}
customLoginOptions={{ paramKey: 'organization' }}
hostedLoginBox
>
<FronteggAppProvider authOptions={{ keepSessionAlive: true }} customLoginOptions={{ paramKey: 'organization' }}>
{children}
</FronteggAppProvider>
</body>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
import { FronteggApiMiddleware } from '@frontegg/nextjs/middleware';

export default FronteggApiMiddleware;

/**
* Option to support multiple origins in single nextjs backend
*
* import type { NextApiRequest, NextApiResponse } from 'next';
*
* export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
* // Add CORS headers after the handler has run
* const allowedOrigins = ['http://localapp1.davidantoon.me:3000', 'http://localapp2.davidantoon.me:3000'];
* const origin = req.headers.origin ?? '';
*
* if (allowedOrigins.includes(origin)) {
* res.setHeader('Access-Control-Allow-Origin', origin);
* } else {
* res.removeHeader('Access-Control-Allow-Origin');
* }
*
* res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH,OPTIONS');
* res.setHeader(
* 'Access-Control-Allow-Headers',
* 'Content-Type, Authorization, x-frontegg-framework, x-frontegg-sdk, frontegg-source'
* );
* res.setHeader('Access-Control-Allow-Credentials', 'true');
*
* return FronteggApiMiddleware(req, res);
* }
*/

export const config = {
api: {
externalResolver: true,
Expand Down
28 changes: 28 additions & 0 deletions packages/example-pages/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState } from 'react';

export function Index() {
const { user, isAuthenticated } = useAuth();
const { switchTenant } = useAuthActions();
const loginWithRedirect = useLoginWithRedirect();
const [state, setState] = useState({ userAgent: '', id: -1 });
const logoutHosted = useLogoutHostedLogin();
Expand Down Expand Up @@ -65,6 +66,33 @@ export function Index() {
<br />
<br />
<Link href='/account/logout'>logout embedded</Link>
<br />
<br />
<br />
<div>
<h2>Tenants:</h2>
{(user?.tenants ?? []).map((tenant) => {
return (
<div key={tenant.tenantId}>
<b>Tenant Id:</b>
{tenant['tenantId']}
<span style={{ display: 'inline-block', width: '20px' }} />
{tenant.tenantId === user?.tenantId ? (
<b>Current Tenant</b>
) : (
<button
onClick={() => {
switchTenant({ tenantId: tenant.tenantId });
}}
>
Switch to tenant
</button>
)}
</div>
);
})}
</div>
<br />
</div>
);
}
Expand Down
35 changes: 34 additions & 1 deletion packages/nextjs/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function removeInvalidHeaders(headers: Record<string, string>) {
* These header is used to identify the tenant for login per tenant feature
*/
export const CUSTOM_LOGIN_HEADER = 'frontegg-login-alias';

/**
* Build fetch request headers, remove invalid http headers
* @param headers - Incoming request headers
Expand All @@ -56,10 +57,33 @@ export function buildRequestHeaders(headers: Record<string, any>): Record<string
let cookie = headers['cookie'];
if (cookie != null && typeof cookie === 'string') {
cookie = cookie.replace(/fe_session-[^=]*=[^;]*$/, '').replace(/fe_session-[^=]*=[^;]*;/, '');

if (config.rewriteCookieByAppId && config.appId) {
cookie = cookie
.split(';')
.filter((cookieStr: string) => !cookieStr.trim().startsWith(`fe_refresh_${config.clientId.replace('-', '')}`))
.join(';');
cookie = cookie.replace(
`fe_refresh_${config.appId.replace('-', '')}`,
`fe_refresh_${config.clientId.replace('-', '')}`
);
}
}
if (cookie != null && typeof cookie === 'object') {
cookie = Object.entries(cookie)
.map(([key, value]) => `${key}=${value}`)
.filter(([key]) => {
if (config.rewriteCookieByAppId && config.appId) {
return key !== `fe_refresh_${config.clientId.replace('-', '')}`;
}
return true;
})
.map(([key, value]) => {
if (config.rewriteCookieByAppId && config.appId && key === `fe_refresh_${config.appId.replace('-', '')}`) {
return `fe_refresh_${config.clientId.replace('-', '')}=${value}`;
} else {
return `${key}=${value}`;
}
})
.join('; ');
}

Expand All @@ -77,6 +101,15 @@ export function buildRequestHeaders(headers: Record<string, any>): Record<string
'x-frontegg-sdk': `@frontegg/nextjs@${sdkVersion.version}`,
};

if (headers['frontegg-requested-application-id']) {
preparedHeaders['frontegg-requested-application-id'] = headers['frontegg-requested-application-id'];
}

const clientIp = headers['cf-connecting-ip'] || headers['x-original-forwarded-for'] || headers['x-forwarded-for'];
if (clientIp) {
preparedHeaders['x-original-forwarded-for'] = clientIp;
}

if (headers[CUSTOM_LOGIN_HEADER]) {
preparedHeaders[CUSTOM_LOGIN_HEADER] = headers[CUSTOM_LOGIN_HEADER];
}
Expand Down
6 changes: 4 additions & 2 deletions packages/nextjs/src/common/FronteggBaseProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import React, { FC, useMemo, useRef } from 'react';
import { FronteggStoreProvider, CustomComponentRegister } from '@frontegg/react-hooks';
import { ContextHolder } from '@frontegg/rest-api';
import { ContextHolder, IUserProfile } from '@frontegg/rest-api';
import type { FronteggProviderProps } from '../types';
import AppContext from './AppContext';
import initializeFronteggApp from '../utils/initializeFronteggApp';
Expand All @@ -28,8 +28,10 @@ const Connector: FC<FronteggProviderProps> = ({ router, appName = 'default', ...
}),
[props]
);
ContextHolder.setOnRedirectTo(onRedirectTo);
ContextHolder.for(appName).setOnRedirectTo(onRedirectTo);

ContextHolder.for(appName).setAccessToken(session?.accessToken ?? null);
ContextHolder.for(appName).setUser(session?.['user'] as any);
useRequestAuthorizeSSR({ app, user, tenants, activeTenant, session });
return (
<AppContext.Provider value={app}>
Expand Down
5 changes: 3 additions & 2 deletions packages/nextjs/src/common/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useLoginActions } from '@frontegg/react-hooks';
import { useLoginActions, useRootState } from '@frontegg/react-hooks';
import { ContextHolder } from '@frontegg/rest-api';
import { buildLogoutRoute } from '../api/urls';

Expand All @@ -10,10 +10,11 @@ import { buildLogoutRoute } from '../api/urls';
*/

export const useLogoutHostedLogin = () => {
const { appName } = useRootState();
const { logout } = useLoginActions();

return (redirectUrl?: string) => {
const contextBaseUrl = ContextHolder.getContext()?.baseUrl;
const contextBaseUrl = ContextHolder.for(appName).getContext()?.baseUrl;
const baseUrl = typeof contextBaseUrl === 'function' ? contextBaseUrl('') : contextBaseUrl;
const finalRedirectUrl = redirectUrl ?? window.location.href;
const logoutRoute = buildLogoutRoute(finalRedirectUrl, baseUrl).asPath;
Expand Down
25 changes: 25 additions & 0 deletions packages/nextjs/src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ export enum EnvVariables {
*/
FRONTEGG_CLIENT_ID = 'FRONTEGG_CLIENT_ID',

/**
* Your Frontegg application ID, get it by visit:
* - For Dev environment [visit](https://portal.frontegg.com/development/applications)
* - For Prod environment [visit](https://portal.frontegg.com/production/applications)
*/
FRONTEGG_APP_ID = 'FRONTEGG_APP_ID',

/**
* Rewrite the cookie name by the Frontegg application ID
* to support multiple Frontegg applications with same domain
*/
FRONTEGG_REWRITE_COOKIE_BY_APP_ID = 'FRONTEGG_REWRITE_COOKIE_BY_APP_ID',

/**
* Your Frontegg application's Client Secret, get it by visit:
* - For Dev environment [visit](https://portal.frontegg.com/development/settings/general)
Expand Down Expand Up @@ -57,6 +70,18 @@ export enum EnvVariables {
*/
FRONTEGG_COOKIE_NAME = 'FRONTEGG_COOKIE_NAME',

/**
* The stateless cookie domain for storing the encrypted JWT
* value as session cookies for supporting getServerSideProps and ServerComponents
*/
FRONTEGG_COOKIE_DOMAIN = 'FRONTEGG_COOKIE_DOMAIN',

/**
* The stateless cookie same site value for storing the encrypted JWT
* default is none, you can set it to 'lax' or 'strict' for more security
*/
FRONTEGG_COOKIE_SAME_SITE = 'FRONTEGG_COOKIE_SAME_SITE',

/**
* When `true`, the initial props will not refresh access token if it's valid.
*/
Expand Down
Loading

0 comments on commit 6701674

Please sign in to comment.