Skip to content

Commit

Permalink
Simplified configuration of custom login URL [SDK-2264] (#268)
Browse files Browse the repository at this point in the history
* Simplify configuration of custom login url

* Address review feedback

* Remove unused import
  • Loading branch information
Widcket committed Feb 2, 2021
1 parent 317def9 commit a2932dc
Show file tree
Hide file tree
Showing 10 changed files with 58 additions and 31 deletions.
21 changes: 17 additions & 4 deletions src/config.ts
Expand Up @@ -278,7 +278,11 @@ export interface AuthorizationParameters extends OidcAuthorizationParameters {
/**
* @ignore
*/
export type NextConfig = Pick<BaseConfig, 'routes' | 'identityClaimFilter'>;
export interface NextConfig extends Pick<BaseConfig, 'identityClaimFilter'> {
routes: {
login: string;
};
}

/**
* ## Configuration properties.
Expand Down Expand Up @@ -317,8 +321,9 @@ export type NextConfig = Pick<BaseConfig, 'routes' | 'identityClaimFilter'>;
* - `AUTH0_IDP_LOGOUT`: See {@link idpLogout}
* - `AUTH0_ID_TOKEN_SIGNING_ALG`: See {@link idTokenSigningAlg}
* - `AUTH0_LEGACY_SAME_SITE_COOKIE`: See {@link legacySameSiteCookie}
* - `AUTH0_POST_LOGOUT_REDIRECT`: See {@link BaseConfig.routes}
* - `NEXT_PUBLIC_AUTH0_LOGIN`: See {@link NextConfig.routes}
* - `AUTH0_CALLBACK`: See {@link BaseConfig.routes}
* - `AUTH0_POST_LOGOUT_REDIRECT`: See {@link BaseConfig.routes}
* - `AUTH0_AUDIENCE`: See {@link BaseConfig.authorizationParams}
* - `AUTH0_SCOPE`: See {@link BaseConfig.authorizationParams}
* - `AUTH0_SESSION_NAME`: See {@link SessionConfig.name}
Expand Down Expand Up @@ -397,8 +402,8 @@ export const getConfig = (params?: ConfigParameters): { baseConfig: BaseConfig;
AUTH0_IDP_LOGOUT,
AUTH0_ID_TOKEN_SIGNING_ALG,
AUTH0_LEGACY_SAME_SITE_COOKIE,
AUTH0_POST_LOGOUT_REDIRECT,
AUTH0_CALLBACK,
AUTH0_POST_LOGOUT_REDIRECT,
AUTH0_AUDIENCE,
AUTH0_SCOPE,
AUTH0_SESSION_NAME,
Expand Down Expand Up @@ -463,11 +468,19 @@ export const getConfig = (params?: ConfigParameters): { baseConfig: BaseConfig;

const nextConfig = {
routes: {
...baseConfig.routes
...baseConfig.routes,
// Other NextConfig Routes go here
login: params?.routes?.login || getLoginUrl()
},
identityClaimFilter: baseConfig.identityClaimFilter
};

return { baseConfig, nextConfig };
};

/**
* @ignore
*/
export const getLoginUrl = (): string => {
return process.env.NEXT_PUBLIC_AUTH0_LOGIN || '/api/auth/login';
};
8 changes: 4 additions & 4 deletions src/frontend/use-user.tsx
Expand Up @@ -33,7 +33,7 @@ export type UserContext = {
/**
* Configure the {@link UserProvider} component.
*
* If you have any server side rendered pages (eg. using `getServerSideProps`), you should get the user from the server
* If you have any server-side rendered pages (eg. using `getServerSideProps`), you should get the user from the server
* side session and pass it to the `<UserProvider>` component via `pageProps` - this will refill the {@link useUser}
* hook with the {@link UserProfile} object. eg
*
Expand All @@ -43,8 +43,8 @@ export type UserContext = {
* import { UserProvider } from '@auth0/nextjs-auth0';
*
* export default function App({ Component, pageProps }) {
* // If you've used `withAuth`, pageProps.user can pre-populate the hook
* // if you haven't used `withAuth`, pageProps.user is undefined so the hook
* // If you've used `withPageAuthRequired`, pageProps.user can pre-populate the hook
* // if you haven't used `withPageAuthRequired`, pageProps.user is undefined so the hook
* // fetches the user from the API route
* const { user } = pageProps;
*
Expand Down Expand Up @@ -87,7 +87,7 @@ const User = createContext<UserContext>({
});

/**
* The `useUser` hook, which will get you the {@link UserProfile} object from the server side session by requesting it
* The `useUser` hook, which will get you the {@link UserProfile} object from the server-side session by requesting it
* from the {@link HandleProfile} API Route handler.
*
* ```js
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/profile.ts
Expand Up @@ -26,9 +26,9 @@ export type HandleProfile = (req: NextApiRequest, res: NextApiResponse, options?
* @ignore
*/
export default function profileHandler(
sessionCache: SessionCache,
getClient: ClientFactory,
getAccessToken: GetAccessToken
getAccessToken: GetAccessToken,
sessionCache: SessionCache
): HandleProfile {
return async (req, res, options): Promise<void> => {
assertReqRes(req, res);
Expand Down
14 changes: 7 additions & 7 deletions src/helpers/with-page-auth-required.ts
Expand Up @@ -35,7 +35,7 @@ export type GetServerSidePropsResultWithSession = GetServerSidePropsResult<{
export type PageRoute = (cts: GetServerSidePropsContext) => Promise<GetServerSidePropsResultWithSession>;

/**
* If you have a custom login url (the default is `/api/auth/login`) you should specify it in `loginUrl`.
* If you have a custom returnTo url you should specify it in `returnTo`.
*
* You can pass in your own `getServerSideProps` method, the props returned from thsi will be merged with the
* user props, eg:
Expand All @@ -49,16 +49,16 @@ export type PageRoute = (cts: GetServerSidePropsContext) => Promise<GetServerSid
* }
*
* export const getServerSideProps = withPageAuthRequired({
* loginUrl: '/api/auth/login',
* returnTo: '/foo',
* async getServerSideProps(ctx) {
* return { props: { customProp: 'foo' } };
* return { props: { customProp: 'bar' } };
* }
* });
* ```
*
* @category Server
*/
export type WithPageAuthRequiredOptions = { getServerSideProps?: GetServerSideProps; loginUrl?: string };
export type WithPageAuthRequiredOptions = { getServerSideProps?: GetServerSideProps; returnTo?: string };

/**
* Wrap your `getServerSideProps` with this method to make sure the user is authenticated before visiting the page.
Expand Down Expand Up @@ -90,23 +90,23 @@ export type WithPageAuthRequired = {
/**
* @ignore
*/
export default function withPageAuthRequiredFactory(getSession: GetSession): WithPageAuthRequired {
export default function withPageAuthRequiredFactory(loginUrl: string, getSession: GetSession): WithPageAuthRequired {
return (
optsOrComponent: WithPageAuthRequiredOptions | ComponentType = {},
csrOpts?: WithPageAuthRequiredCSROptions
): any => {
if (typeof optsOrComponent === 'function') {
return withPageAuthRequiredCSR(optsOrComponent, csrOpts);
}
const { getServerSideProps, loginUrl = '/api/auth/login' } = optsOrComponent;
const { getServerSideProps, returnTo } = optsOrComponent;
return async (ctx: GetServerSidePropsContext): Promise<GetServerSidePropsResultWithSession> => {
assertCtx(ctx);
const session = getSession(ctx.req, ctx.res);
if (!session?.user) {
// 10 - redirect
// 9.5.4 - unstable_redirect
// 9.4 - res.setHeaders
return { redirect: { destination: `${loginUrl}?returnTo=${ctx.resolvedUrl}`, permanent: false } };
return { redirect: { destination: `${loginUrl}?returnTo=${returnTo || ctx.resolvedUrl}`, permanent: false } };
}
let ret: any = { props: {} };
if (getServerSideProps) {
Expand Down
10 changes: 5 additions & 5 deletions src/index.ts
Expand Up @@ -47,7 +47,7 @@ import {
} from './helpers';
import { InitAuth0, SignInWithAuth0 } from './instance';
import version from './version';
import { getConfig, ConfigParameters } from './config';
import { getConfig, getLoginUrl, ConfigParameters } from './config';

let instance: SignInWithAuth0;

Expand All @@ -73,13 +73,13 @@ export const initAuth0: InitAuth0 = (params) => {

// Init Next layer (with next config)
const getSession = sessionFactory(sessionCache);
const getAccessToken = accessTokenFactory(getClient, nextConfig, sessionCache);
const getAccessToken = accessTokenFactory(nextConfig, getClient, sessionCache);
const withApiAuthRequired = withApiAuthRequiredFactory(sessionCache);
const withPageAuthRequired = withPageAuthRequiredFactory(getSession);
const withPageAuthRequired = withPageAuthRequiredFactory(nextConfig.routes.login, getSession);
const handleLogin = loginHandler(baseHandleLogin);
const handleLogout = logoutHandler(baseHandleLogout);
const handleCallback = callbackHandler(baseHandleCallback);
const handleProfile = profileHandler(sessionCache, getClient, getAccessToken);
const handleProfile = profileHandler(getClient, getAccessToken, sessionCache);
const handleAuth = handlerFactory({ handleLogin, handleLogout, handleCallback, handleProfile });

return {
Expand All @@ -99,7 +99,7 @@ export const getSession: GetSession = (...args) => getInstance().getSession(...a
export const getAccessToken: GetAccessToken = (...args) => getInstance().getAccessToken(...args);
export const withApiAuthRequired: WithApiAuthRequired = (...args) => getInstance().withApiAuthRequired(...args);
export const withPageAuthRequired: WithPageAuthRequired = (...args: any[]): any =>
withPageAuthRequiredFactory(getSession)(...args);
withPageAuthRequiredFactory(getLoginUrl(), getSession)(...args);
export const handleLogin: HandleLogin = (...args) => getInstance().handleLogin(...args);
export const handleLogout: HandleLogout = (...args) => getInstance().handleLogout(...args);
export const handleCallback: HandleCallback = (...args) => getInstance().handleCallback(...args);
Expand Down
2 changes: 1 addition & 1 deletion src/session/get-access-token.ts
Expand Up @@ -51,8 +51,8 @@ export type GetAccessToken = (
* @ignore
*/
export default function accessTokenFactory(
getClient: ClientFactory,
config: NextConfig,
getClient: ClientFactory,
sessionCache: SessionCache
): GetAccessToken {
return async (req, res, accessTokenRequest): Promise<GetAccessTokenResult> => {
Expand Down
6 changes: 5 additions & 1 deletion tests/config.test.ts
Expand Up @@ -88,7 +88,11 @@ describe('config params', () => {
'at_hash',
'c_hash'
],
routes: { callback: '/api/auth/callback', postLogoutRedirect: '' }
routes: {
login: '/api/auth/login',
callback: '/api/auth/callback',
postLogoutRedirect: ''
}
});
});

Expand Down
1 change: 0 additions & 1 deletion tests/fixtures/test-app/pages/_document.tsx
@@ -1,7 +1,6 @@
import React from 'react';
import Document from 'next/document';

// TODO: Stubbing out the document to resolve "Invalid hook call"
class MyDocument extends Document {
static getInitialProps(ctx): any {
return Document.getInitialProps(ctx);
Expand Down
21 changes: 16 additions & 5 deletions tests/helpers/with-page-auth-required.test.ts
Expand Up @@ -25,16 +25,16 @@ describe('with-page-auth-required ssr', () => {
expect(data).toMatch(/<div>Blank Document<\/div>/);
});

test('use a custom login url', async () => {
const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { loginUrl: '/api/foo' } });
test('accept a custom returnTo url', async () => {
const baseUrl = await setup(withoutApi, { withPageAuthRequiredOptions: { returnTo: '/foo' } });
const {
res: { statusCode, headers }
} = await get(baseUrl, '/protected', { fullResponse: true });
expect(statusCode).toBe(307);
expect(headers.location).toBe('/api/foo?returnTo=/protected');
expect(headers.location).toBe('/api/auth/login?returnTo=/foo');
});

test('use custom server side props', async () => {
test('accept custom server-side props', async () => {
const spy = jest.fn().mockReturnValue({ props: {} });
const baseUrl = await setup(withoutApi, {
withPageAuthRequiredOptions: {
Expand All @@ -49,7 +49,18 @@ describe('with-page-auth-required ssr', () => {
expect(spy).toHaveBeenCalledWith(expect.objectContaining({ req: expect.anything(), res: expect.anything() }));
});

test('is a noop when invoked as a client side protection from the server', async () => {
test('use a custom login url', async () => {
process.env.NEXT_PUBLIC_AUTH0_LOGIN = '/api/foo';
const baseUrl = await setup(withoutApi);
const {
res: { statusCode, headers }
} = await get(baseUrl, '/protected', { fullResponse: true });
expect(statusCode).toBe(307);
expect(headers.location).toBe('/api/foo?returnTo=/protected');
delete process.env.NEXT_PUBLIC_AUTH0_LOGIN;
});

test('is a noop when invoked as a client-side protection from the server', async () => {
const baseUrl = await setup(withoutApi);
const cookieJar = await login(baseUrl);
const {
Expand Down
2 changes: 1 addition & 1 deletion tests/session/session.test.ts
Expand Up @@ -12,7 +12,7 @@ describe('session', () => {
expect(
fromTokenSet(new TokenSet({ id_token: makeIdToken({ foo: 'bar', bax: 'qux' }) }), {
identityClaimFilter: ['baz'],
routes: { callback: '', postLogoutRedirect: '' }
routes: { login: '', callback: '', postLogoutRedirect: '' }
}).user
).toEqual({
aud: '__test_client_id__',
Expand Down

0 comments on commit a2932dc

Please sign in to comment.