1"use strict";(self.webpackChunk_vulcanjs_docusaurus=self.webpackChunk_vulcanjs_docusaurus||[]).push([[7565],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,a,r=function(e,t){if(null==e)return{};var n,a,r={},o=Object.keys(e);for(a=0;a<o.length;a++)n=o[a],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a<o.length;a++)n=o[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),u=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=u(e.components);return a.createElement(l.Provider,{value:t},e.children)},p="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),p=u(n),d=r,m=p["".concat(l,".").concat(d)]||p[d]||h[d]||o;return n?a.createElement(m,i(i({ref:t},c),{},{components:n})):a.createElement(m,i({ref:t},c))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[p]="string"==typeof e?e:r,i[1]=s;for(var u=2;u<o;u++)i[u]=n[u];return a.createElement.apply(null,i)}return a.createElement.apply(null,n)}d.displayName="MDXCreateElement"},7457:(e,t,n)=>{n.r(t),n.d(t,{contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>l});var a=n(7462),r=(n(7294),n(3905));const o={},i="Authentication",s={unversionedId:"vulcan-next/features/authentication",id:"vulcan-next/features/authentication",title:"Authentication",description:"Custom app-level setup",source:"@site/docs/vulcan-next/features/authentication.md",sourceDirName:"vulcan-next/features",slug:"/vulcan-next/features/authentication",permalink:"/docs/vulcan-next/features/authentication",editUrl:"https://github.com/VulcanJS/vulcan-npm/tree/main/docusaurus/docs/vulcan-next/features/authentication.md",tags:[],version:"current",frontMatter:{},sidebar:"mainSidebar",previous:{title:"Architecture and others",permalink:"/docs/vulcan-next/features/architecture-and-others"},next:{title:"GraphQL Client",permalink:"/docs/vulcan-next/features/graphql-client"}},l=[{value:"Custom app-level setup",id:"custom-app-level-setup",children:[],level:2},{value:"Why no reusable packages?",id:"why-no-reusable-packages",children:[],level:2},{value:"Passport authentication",id:"passport-authentication",children:[],level:2},{value:"Flows",id:"flows",children:[{value:"Signup",id:"signup",children:[],level:3},{value:"Login",id:"login",children:[],level:3},{value:"Auth flow",id:"auth-flow",children:[],level:3},{value:"Logout",id:"logout",children:[],level:3}],level:2},{value:"API",id:"api",children:[],level:2},{value:"UI",id:"ui",children:[],level:2},{value:"Experimental SSR redirection (deprecated)",id:"experimental-ssr-redirection-deprecated",children:[],level:2}],u={toc:l};function c(e){let{components:t,...n}=e;return(0,r.kt)("wrapper",(0,a.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"authentication"},"Authentication"),(0,r.kt)("h2",{id:"custom-app-level-setup"},"Custom app-level setup"),(0,r.kt)("p",null,"We use stateful, token-based (with symmetrical encryption) password-based authentication."),(0,r.kt)("p",null,"At the time of writing (2021 11), authentication is used only to protect API routes.\nYou don't really need to protect web pages, redirections based on the ",(0,r.kt)("inlineCode",{parentName:"p"},"useUser")," hook\nare sufficient."),(0,r.kt)("p",null,"We will introduce ",(0,r.kt)("a",{parentName:"p",href:"https://nextjs.org/docs/advanced-features/middleware"},"Middlewares")," later on for protecting web pages, but think of it as\na very advanced feature. It serves only to protect sensitive content such as paid pages, and\neven then you don't really need this feature, it's just a perf optimization."),(0,r.kt)("h2",{id:"why-no-reusable-packages"},"Why no reusable packages?"),(0,r.kt)("p",null,"Vulcan has been there for a long while, and we've seen it all. We are still disatisfied by the state of the art. Many reusable libraries have popped up over time, from ",(0,r.kt)("a",{parentName:"p",href:"https://docs.meteor.com/api/accounts.html"},"Meteor accounts")," system to ",(0,r.kt)("a",{parentName:"p",href:"https://www.accountsjs.com/"}
1,"Account-js")," and now ",(0,r.kt)("a",{parentName:"p",href:"https://next-auth.js.org/"},"Next auth"),"."),(0,r.kt)("p",null,"None of them is canonical, none of them is really easy to use, because none of them is based on any actual kind of standard. ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/lbke/research/blob/main/auth.md"},"You can help us fix that by contributing to our research here.")),(0,r.kt)("p",null,"They are certainly working very well, but they are not transparent on the way they work under the hood: are tokens stored in HTTP cookies or localStorage? What algorithms are used? Are they symmetrical or not, can you check auth without a server, will they call a database???"),(0,r.kt)("p",null,"With a custom implementation, you have full control over these factors. We prefer transparent code over reusable blackboxes. However, we still recommend ",(0,r.kt)("a",{parentName:"p",href:"https://next-auth.js.org/"},"Next Auth")," if you believe it's a fit for you."),(0,r.kt)("h2",{id:"passport-authentication"},"Passport authentication"),(0,r.kt)("p",null,"See the home page footer to access signup, login, logout and profile page. Implementation is based on Next official example ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/vercel/next.js/tree/canary/examples/with-passport-and-next-connect"},"with Passport and Next Connect")),(0,r.kt)("h2",{id:"flows"},"Flows"),(0,r.kt)("h3",{id:"signup"},"Signup"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"When an user signs up with email and password, we hash the password and store only the hashed version (+ salt)")),(0,r.kt)("h3",{id:"login"},"Login"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"When a user logs in, we hash the password and check it against the password stored in the database"),(0,r.kt)("li",{parentName:"ul"},"If we found the user, we return a Set-Cookie header with a token, hashed with a secret value")),(0,r.kt)("h3",{id:"auth-flow"},"Auth flow"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"In subsequent requests, the cookie is automatically added to the request headers by your browser.\nIt is set as ",(0,r.kt)("inlineCode",{parentName:"li"},"httpOnly")," so you can't manipulate the token in JS."),(0,r.kt)("li",{parentName:"ul"},'The protected API route will check the token, decrypt it using the same secret used for encryption\n(hence the "symmetrical" encryption). If the token can be decrypted with this secret, it means it\'s authentic.'),(0,r.kt)("li",{parentName:"ul"},"We also check that the _id matches a user in the database (that user was not deleted already)")),(0,r.kt)("h3",{id:"logout"},"Logout"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"When a user logs out, the auth token is removed from the browser cookies using a Set-Header cookie")),(0,r.kt)("h2",{id:"api"},"API"),(0,r.kt)("p",null,"We provide basic REST endpoints for authentication in ",(0,r.kt)("inlineCode",{parentName:"p"},"/pages/api/account"),"."),(0,r.kt)("p",null,"Note that we do not use GraphQL for this use case, eventhough it's technically possible.\nWe believe that GraphQL should be used for the use cases where it shines: getting data,\nand CRUD operations. "),(0,r.kt)("p",null,"Authentication workflow, or file upload for instance, do not really\nfall into this category, so we prefer using more basic, independant, REST endpoints for those use cases."),(0,r.kt)("h2",{id:"ui"},"UI"),(0,r.kt)("p",null,"We provide basic UI in the ",(0,r.kt)("inlineCode",{parentName:"p"},"/pages/account"),"."),(0,r.kt)("h2",{id:"experimental-ssr-redirection-deprecated"},"Experimental SSR redirection (deprecated)"),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"This feature is experimental and not useful in most scenarios.")," We advise to stick to client-side only patterns. ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/VulcanJS/vulcan-next/issues/71"},"See relevant issue"),"."),(0,r.kt)("p",null,"See ",(0,r.kt)("inlineCode",{parentName:"p"},"src/pages/vn/debug/private.tsx.old")," for a demo. You can use ",(0,r.kt)("inlineCode",{parentName:"p"},"withPrivateAccess")," HOC to make a page private and handle redirections correctly in all situations (server-side, client-side, in the context of a static export etc.)."))}c.isMDXComponent=!0}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.