Repository: auth0/nextjs-auth0
Pull request #53 (2019-12-08 16:13:19)

Add 'cookieDomain' setting - formatted
This PR continues where #31 stopped. You can refer to its description as only formatting changes were suggested last.


Patch:
diff --git a/README.md b/README.md
index a40f08cb..7ab97c31 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ yarn add @auth0/nextjs-auth0
 
 ### Auth0 Configuration
 
-Create a **Regular Web Application** in the [Auth0 Dashboard](https://manage.auth0.com/). If you're using an existing application you'll want to verify that the following settings are configured as follows:
+Create a **Regular Web Application** in the [Auth0 Dashboard](https://manage.auth0.com/). If you're using an existing application you'll want to very that the following settings are configured as follows:
 
  - **Json Web Token Signature Algorithm**: `RS256`
  - **OIDC Conformant**: `True`
@@ -68,6 +68,8 @@ export default initAuth0({
     cookieSecret: '<RANDOMLY_GENERATED_SECRET>',
     // The cookie lifetime (expiration) in seconds. Set to 8 hours by default.
     cookieLifetime: 60 * 60 * 8,
+    // The cookie domain this should run on. Leave it blank to restrict it to your domain.
+    cookieDomain: 'your-domain.com',
     // Store the id_token in the session. Defaults to false.
     storeIdToken: false,
     // Store the access_token in the session. Defaults to false.
@@ -129,7 +131,6 @@ You can optionally send extra parameters to Auth0 to influence the transaction,
 - Showing the login page
 - Filling in the user's email address
 - Exposing information to the custom login page (eg: to show the signup tab)
-- Using a custom `state`
 
 ```js
 import auth0 from '../../utils/auth0';
@@ -141,7 +142,6 @@ export default async function login(req, res) {
         login_hint: 'foo@acme.com',
         ui_locales: 'nl',
         scope: 'some other scope',
-        state: 'a custom state',
         foo: 'bar'
       }
     });
@@ -244,6 +244,7 @@ export default initAuth0({
   session: {
     cookieSecret: '<RANDOMLY_GENERATED_SECRET>',
     cookieLifetime: 60 * 60 * 8,
+    cookieDomain: 'https://mycompany.com',
     storeAccessToken: true
   }
 });
diff --git a/src/handlers/login.ts b/src/handlers/login.ts
index 57de6072..cff10cce 100644
--- a/src/handlers/login.ts
+++ b/src/handlers/login.ts
@@ -30,7 +30,6 @@ export interface AuthorizationParameters {
   max_age?: string;
   prompt?: string;
   scope?: string;
-  state?: string;
   ui_locales?: string;
   [key: string]: unknown;
 }
@@ -45,10 +44,12 @@ export default function loginHandler(settings: IAuth0Settings, clientProvider: I
       throw new Error('Response is not available');
     }
 
-    const { state = base64url(randomBytes(48)), ...authParams } = (options && options.authParams) || {};
+    // Generate the state
+    const state = base64url(randomBytes(48));
 
     // Create the authorization url.
     const client = await clientProvider();
+    const authParams = (options && options.authParams) || {};
     const authorizationUrl = client.authorizationUrl({
       redirect_uri: settings.redirectUri,
       scope: settings.scope,
diff --git a/src/instance.ts b/src/instance.ts
index 7a7288a5..8a4dc290 100644
--- a/src/instance.ts
+++ b/src/instance.ts
@@ -1,7 +1,6 @@
 import { IncomingMessage, ServerResponse } from 'http';
 import { NextApiRequest, NextApiResponse } from 'next';
 
-import { LoginOptions } from './handlers/login';
 import { ISession } from './session/session';
 import { CallbackOptions } from './handlers/callback';
 import { IApiRoute } from './handlers/require-authentication';
@@ -10,7 +9,7 @@ export interface ISignInWithAuth0 {
   /**
    * Login handler which will redirect the user to Auth0.
    */
-  handleLogin: (req: IncomingMessage, res: ServerResponse, options: LoginOptions) => Promise<void>;
+  handleLogin: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
 
   /**
    * Callback handler which will complete the transaction and create a local session.
diff --git a/src/session/cookie-store/index.ts b/src/session/cookie-store/index.ts
index a8cfe872..269836a0 100644
--- a/src/session/cookie-store/index.ts
+++ b/src/session/cookie-store/index.ts
@@ -40,7 +40,7 @@ export default class CookieSessionStore implements ISessionStore {
    */
   async save(req: IncomingMessage, res: ServerResponse, session: ISession): Promise<void> {
     const {
-      cookieSecret, cookieName, cookiePath, cookieLifetime
+      cookieSecret, cookieName, cookiePath, cookieLifetime, cookieDomain
     } = this.settings;
 
     const {
@@ -65,7 +65,8 @@ export default class CookieSessionStore implements ISessionStore {
       name: cookieName,
       value: encryptedSession,
       path: cookiePath,
-      maxAge: cookieLifetime
+      maxAge: cookieLifetime,
+      domain: cookieDomain
     });
   }
 }
diff --git a/src/session/cookie-store/settings.ts b/src/session/cookie-store/settings.ts
index 1bc9513f..afe3c3e2 100644
--- a/src/session/cookie-store/settings.ts
+++ b/src/session/cookie-store/settings.ts
@@ -22,6 +22,13 @@ export interface ICookieSessionStoreSettings {
    */
   cookiePath?: string;
 
+  /**
+   * The Domain option to set on the cookie.
+   * Defaults to omitting the option, which restricts the cookie
+   * to the host of the current document URL, not including subdomains.
+   */
+  cookieDomain?: string;
+
   /**
    * Save the id_token in the cookie.
    * Defaults to 'false'
@@ -50,6 +57,8 @@ export default class CookieSessionStoreSettings {
 
   readonly cookiePath: string;
 
+  readonly cookieDomain: string;
+
   readonly storeIdToken: boolean;
 
   readonly storeAccessToken: boolean;
@@ -73,6 +82,8 @@ export default class CookieSessionStoreSettings {
 
     this.cookieLifetime = settings.cookieLifetime || 60 * 60 * 8;
 
+    this.cookieDomain = settings.cookieDomain || '';
+
     this.cookiePath = settings.cookiePath || '/';
     if (!this.cookiePath || !this.cookiePath.length) {
       throw new Error('The cookiePath setting is empty or null');
diff --git a/src/utils/cookies.ts b/src/utils/cookies.ts
index 6af1083d..fbe653ca 100644
--- a/src/utils/cookies.ts
+++ b/src/utils/cookies.ts
@@ -26,6 +26,11 @@ interface ICookie {
    * The path of the cookie
    */
   path?: string;
+
+  /**
+   * The domain of the cookie
+   */
+  domain?: string;
 }
 
 /**
@@ -76,7 +81,8 @@ function serializeCookie(cookie: ICookie, secure: boolean): string {
     expires: new Date(Date.now() + cookie.maxAge * 1000),
     httpOnly: true,
     secure,
-    path: cookie.path
+    path: cookie.path,
+    domain: cookie.domain
   });
 }
 


Test patch:
diff --git a/tests/handlers/login.test.ts b/tests/handlers/login.test.ts
index 80c91b2d..e0c81723 100644
--- a/tests/handlers/login.test.ts
+++ b/tests/handlers/login.test.ts
@@ -87,22 +87,6 @@ describe('login handler', () => {
     expect(headers.location)
       .toContain('&max_age=123&login_hint=foo%40acme.com&ui_locales=nl&foo=bar');
   });
-
-  test('should allow sending custom state to the authorization server', async () => {
-    loginOptions = {
-      authParams: {
-        state: 'custom-state',
-      }
-    };
-    const { statusCode, headers } = await getAsync({
-      url: httpServer.getUrl(),
-      followRedirect: false
-    });
-
-    expect(statusCode).toBe(302);
-    expect(headers.location)
-      .toContain('&state=custom-state');
-  });
 });
 
 describe('withApi login handler', () => {
diff --git a/tests/session/cookie-store.test.ts b/tests/session/cookie-store.test.ts
index 20ba0652..4349dbf8 100644
--- a/tests/session/cookie-store.test.ts
+++ b/tests/session/cookie-store.test.ts
@@ -47,6 +47,40 @@ describe('cookie store', () => {
     });
   });
 
+  describe('with cookie domain', () => {
+    describe('configured', () => {
+      const store = getStore({
+        cookieDomain: ".quirk.fyi",
+      })
+
+      test('should set cookie domain', async () => {
+        const { req, res, setHeaderFn } = getRequestResponse();
+        await store.save(req, res, {
+          user: { sub: '123' },
+          createdAt: Date.now()
+        });
+
+        const [, cookie] = setHeaderFn.mock.calls[0];
+        expect(parse(cookie).Domain).toBe(".quirk.fyi")
+      })
+    }),
+
+    describe('not configured', () => {
+      const store = getStore({})
+
+      test('should not set the cookie domain', async () => {
+        const { req, res, setHeaderFn } = getRequestResponse();
+        await store.save(req, res, {
+          user: { sub: '123' },
+          createdAt: Date.now()
+        });
+
+        const [, cookie] = setHeaderFn.mock.calls[0];
+        expect(parse(cookie).Domain).toBeUndefined()
+      })
+    })
+  })
+
   describe('with storeAccessToken', () => {
     describe('configured', () => {
       const store = getStore({});