The problem I'm facing is that I'm unable to get the absolute URL in the production build when using getStaticPaths and getStaticProps
export async function getStaticPaths() {
const url =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://websitename.vercel.app";
const res = await fetch(`${url}/api/posts`);
const posts = await res.json();
console.log("posts: ", posts);
const paths = posts.map(({ slug }) => ({
params: { slug },
}));
console.log("Paths: ", paths);
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
console.log("params: ", params);
const url =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://websitename.vercel.app";
const res = await fetch(`${url}/api/post`, {
method: "POST",
body: params.slug,
});
const post = await res.json();
return {
props: { post },
};
}
It works fine in the development build but when it comes to production it fails because the hardcoded https://websitename.vercel.app is not the one generated by vercel. The URL generated by vercel is something like this websitename-q1hdjf6c2.vercel.app.
How do I fix this?
you can use ${process.env.VERCEL_URL}/my/route.
Check
Vercel environment variables
Unfortunately, since static site building process is done without an actual site running, the full URL is not accessible there. You can only access the full URL on the client side in the global window object.
You can also set an environment variable after Vercel generates a URL for your application and use it from there.
This seem to work
import { useRouter } from "next/router";
...
const router = useRouter();
...
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "";
url = origin + router.asPath;
or
import { useRouter } from "next/router";
...
const {asPath} = useRouter();
...
const url = process.env.NEXT_PUBLIC_DOMAIN + asPath;
...
.env
NEXT_PUBLIC_DOMAIN='https://webaddress.com'
Related
I'm familiar with Svelte but completely new to Sveltekit. I'm trying to build a Sveltekit app from scratch using AWS Cognito as the authorization tool without using AWS Amplify, using the amazon-cognito-identity-js sdk. I've got all the functionality working as far as login, registration, and verification, but I can't seem to get a handle on how to store the session data for the structure I've built.
I've been trying to translate the strategies from this tutorial, based in React, to Sveltekit -- (AWS Cognito + React JS Tutorial - Sessions and Logging out (2020) [Ep. 3]) https://www.youtube.com/watch?v=R-3uXlTudSQ
and this REPL to understand using context in Svelte ([AD] Combining the Context API with Stores) https://svelte.dev/repl/7df82f6174b8408285a1ea0735cf2ff0
To elaborate, I've got my structure like so (only important parts shown):
src
|
|-- components
|-- ...
|-- status.svelte
|-- routes
|
|-- dashboard
|-- onboarding
|-- __layout.reset.svelte
|-- login.svelte
|-- signup.svelte
|-- verify.svelte
|-- ...
|-- settings
|-- __layout.svelte
|-- index.svelte
|-- styles
|-- utils
|-- cognitoTools.ts
|-- stores.ts
I wanted to have a separate path for my onboarding pages, hence the sub-folder. My cognito-based functions reside within cognitoTools.ts. An example of a few functions look like:
export const Pool = new CognitoUserPool(poolData);
export const User = (Username: string): any => new CognitoUser({ Username, Pool });
export const Login = (username: string, password: string): any => {
return new Promise ((resolve, reject) => User(username).authenticateUser(CognitoAuthDetails(username, password), {
onSuccess: function(result) {
console.log('CogTools login success result: ', result);
resolve(result)
},
onFailure: function(err) {
console.error('CogTools login err: ', err);
reject(err)
}
}))
}
I'm able to then use the methods freely anywhere:
// src/routes/onboarding/login.svelte
import { Login, Pool } from '#utils/cognitoTools'
import { setContext, getContext } from 'svelte'
let username;
let password;
let session = writeable({});
let currentSession;
// Setting our userSession store to variable that will be updated
$: userSession.set(currentSession);
// Attempt to retrieve getSession func defined from wrapper component __layout.svelte
const getSession = getContext('getSession');
const handleSubmit = async (event) => {
event.preventDefault()
Login(username, password, rememberDevice)
.then(() => {
getSession().then((session) => {
// userSession.set(session);
currentSession = session;
setContext('currentSession', userSession);
})
})
}
...
// src/routes/__layout.svelte
...
const getSession = async () => {
return await new Promise((resolve, reject) => {
const user = Pool.getCurrentUser();
if (user) {
user.getSession((err, session) => {
console.log('User get session result: ', (err ? err : session));
err ? reject() : resolve(session);
});
} else {
console.log('get session no user found');
reject();
}
})
}
setContext('getSession', getSession)
Then, I've been trying to retrieve the session in src/components/status.svelte or src/routes/__layout.svelte (as I think I understand context has to be set in the top level components, and can then be used by indirect child components) to check if the context was set correctly.
Something like:
let status = false;
const user = getContext('currentSession');
status = user ? true : false;
I'm running in circles and I know I'm so close to the answer. How do I use reactive context with my current file structure to accomplish this?
I don't know much about the sdk, so I can't help you with your code above. But I also built an app that uses cognito for auth, and I can share some snippets on how to do it from scratch.
Implement a login form. I have my basic app skeleton (navbar, footer, main slot) in _layout.svelte, and it is configured to show the Login.svelte component and not the main slot if the user is not logged in.
file: __layout.svelte
<script context="module">
export async function load({ session }) {
return {
props: {
user: session.user,
}
}
}
</script>
<script>
import "../app.css";
import Login from "$components/Login.svelte";
export let user
</script>
<svelte:head>
<title>title</title>
</svelte:head>
{#if user}
<header>
</header>
<main>
<slot />
</main>
{:else}
<Login />
{/if}
file: Login.svelte
<form action="/" method="GET">
<input type="hidden" name="action" value="signin" />
<button type="submit" >Sign in</button>
</form>
Handle the login
I choose to do this as a svelte endpoint paired with the index. It keeps the routing super simple. You could do separate login.js and logout.js endpoints if you prefer. Just change your url in the form above.
file: index.js
import { v4 as uuid } from '#lukeed/uuid'
import db from '$lib/db'
const domain = import.meta.env.VITE_COGNITO_DOMAIN
const clientId = import.meta.env.VITE_COGNITO_CLIENT_ID
const redirectUri = import.meta.env.VITE_COGNITO_REDIRECT_URI
const logoutUri = import.meta.env.VITE_COGNITO_LOGOUT_URI
export const get = async (event) => {
const action = event.url.searchParams.get('action')
if (action === 'signin') {
// Hard to guess random string. Used to protect against forgery attacks.
// Should add check in callback that the state matches to prevent forgery
const state = uuid()
return {
status: 302,
headers: {
location: `https://${domain}/oauth2/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=openid+email+profile&state=${state}`,
},
}
}
if (action === 'signout') {
// delete this session from database
if (event.locals.session_id) {
await db.sessions.deleteMany({
where: { session_id: event.locals.session_id }
})
}
return {
status: 302,
headers: {
location: `https://${domain}/logout?client_id=${clientId}&logout_uri=${logoutUri}`
}
}
}
return {}
}
Handle the callback from AWS cognito. Again, I have the callback simply point to the root url. All authentication for me is handled at "/". The heavy lifting is done by hooks.js. This is your SK middleware. It for me is the sole arbiter of the user's authentication state, just because I like to keep it easy for me to understand.
file: hooks.js
import { v4 as uuid } from '#lukeed/uuid'
import cookie from 'cookie'
import db from '$lib/db'
const domain = import.meta.env.VITE_COGNITO_DOMAIN
const clientId = import.meta.env.VITE_COGNITO_CLIENT_ID
const clientSecret = import.meta.env.VITE_COGNITO_CLIENT_SECRET
const redirectUri = import.meta.env.VITE_COGNITO_REDIRECT_URI
const tokenUrl = `https://${domain}/oauth2/token`
const profileUrl = `https://${domain}/oauth2/userInfo`
export const handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '')
event.locals.session_id = cookies.session_id // this will be overwritten by a new session_id if this is a callback
if (event.locals.session_id) {
// We have a session cookie, check to see if it is valid.
// Do this by checking against your session db or session store or whatever
// If not valid, or if it is expired, set event.locals.session_id to null
// This will cause the cookie to be deleted below
// In this example, we just assume it's valid
}
if ( (!event.locals.session_id) && event.url.searchParams.get('code') && event.url.searchParams.get('state') ) {
// No valid session cookie, check to see if this is a callback
const code = event.url.searchParams.get('code')
const state = event.url.searchParams.get('state')
// Change this to try, catch for error handling
const token = await getToken(code, state)
if (token != null) {
let cognitoUser = await getUser(token)
event.locals.session_id = uuid()
// Add the value to the db
await db.sessions.create({
data: {
session_id: event.locals.session_id,
user_id: cognitoUser.username,
created: Date()
},
})
let user = await db.users.findUnique({
where: {
user_id: cognitoUser.username,
}
})
event.locals.user = user
event.locals.authorized = true
}
}
const response = await resolve(event);
// This will delete the cookie if event.locals.session_id is null
response.headers.set(
'set-cookie',
cookie.serialize('session_id', event.locals.session_id, {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, // one week
})
)
return response;
}
export async function getSession(event) {
return {
user: event.locals.user,
}
}
const getToken = async (code, state) => {
let authorization = Buffer.from(`${clientId}:${clientSecret}`).toString('base64')
const res = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${authorization}`,
},
body: `grant_type=authorization_code&client_id=${clientId}&code=${code}&state=${state}&redirect_uri=${redirectUri}`
})
if (res.ok) {
const data = await res.json()
return data.access_token
} else {
return null
}
}
const getUser = async (token) => {
const res = await fetch(profileUrl, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (res.ok) {
return res.json()
} else {
return null
}
}
Lastly, getting the auth state. In a client-side route, this is done via the page load function. I put this in __layout to have it available on all .svelte routes. You can see it at the top of the __layout file above. For SSR endpoints, you can just access event.locals directly.
NOTE: All of the env vars are set in your .env file and will be imported by vite. This only happens when you start your app, so if you add/change them, you need to restart it.
I don't know if this helps at all since it is so different from your app structure, but maybe you will get some ideas from it.
I don't know what the problem is you run into exactly, but one thing that stands out to me is that you are calling setContext when it's "too" late. You can only call getContext/setContext within component initialization. See this answer for more details: Is there a way to use svelte getContext etc. svelte functions in Typescript files?
If this is the culprit and you are looking for a way how to get the session then: Use context in combination with stores:
<!-- setting the session -->
<script>
// ...
const session = writable(null);
setContext('currentSession', session);
// ...
Login...then(() => ...session.set(session));
</script>
<!-- setting the session -->
<script>
// ..
const user = getContext('currentSession');
// ..
status = $user ? true : false;
</script>
Another thing that stands out to me - but is too long/vague for a StackOverflow answer - is that you are not using SvelteKit's features to achieve this behavior. You could look into load and use stuff in __layout to pass the session down to all children. I'm no sure if this is of any advantage for you though since you are maybe planning to do a SPA anyway and therefore don't need such SvelteKit features.
I want to get the page's full URL or site hostname like the image below on Static Site Generator.
I will try with window.location.hostname, but it doesn't work.
The error: window not defined.
If you want the hostname inside getInitialProps on server side, still you can get it from req
Home.getInitialProps = async(context) => {
const { req, query, res, asPath, pathname } = context;
if (req) {
let host = req.headers.host // will give you localhost:3000
}
}
With server-side rendering (getServerSideProps), you can use context.req.headers.host:
import type { GetServerSideProps, NextPage } from "next";
type Props = { host: string | null };
export const getServerSideProps: GetServerSideProps<Props> =
async context => ({ props: { host: context.req.headers.host || null } });
const Page: NextPage<Props> = ({ host }) => <p>Welcome to {host || "unknown host"}!</p>;
export default Page;
But with static generation (getStaticProps), the hostname is not available, because there is no request to get it from. In general, a server doesn't know its own public hostname, so you need to tell it. Using Next.js environment variables, put this in .env.local:
HOST=example.com
Then access it with process.env['HOST']:
import type { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps<Props> =
async context => ({ props: { host: process.env['HOST'] || null }});
If you want to get the full URL:
import { useRouter } from 'next/router';
const { asPath } = useRouter();
const origin =
typeof window !== 'undefined' && window.location.origin
? window.location.origin
: '';
const URL = `${origin}${asPath}`;
console.log(URL);
The place where you are accessing the window make sure you add a check so that code is executed only on the browser and no during SSG"
if (typeof window !== 'undefined') {
const hostname = window.location.hostname;
}
Update:
If you have specified basePath in next.config.js:
module.exports = {
basePath: 'https://www.example.com/docs',
}
Then using useRouter, you can access the base path:
import { useRouter } from 'next/router'
function Component() {
const router = useRouter();
console.log({ basePath: router.basePath});
// { basePath: 'https://www.example.com/docs' }
...
}
But if you have a relative base path then you can use the first approach
Consider this package > next-absolute-url
import absoluteUrl from 'next-absolute-url'
const { origin } = absoluteUrl(req)
const apiURL = `${origin}/api/job.js`
If you deployed your Next.js app with now the apiURL will be something like https://your-app.now.sh/api/job.js.
However, if you are running the app locally the apiURL will be http://localhost:8000/api/job.js instead.
Using typeof window !== 'undefined' is the secure way. if (window) {} will run you into problems.
const hostname = typeof window !== 'undefined' && window.location.hostname ? window.location.hostname : '';
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
Using above code will give you the frontend/outside hostname/origin the client using: example.com, www.example.com, www.example.com:80 and so on, not the localhost stuff. useRouter() will return the server side hostname/origin (localhost, localhost:3000)
I believe you're better of doing this with a combination of useRouter and useEffect hooks. In my case I wanted to dynamically set the og:url of my webpage. This is what I did. We have router.pathname as a dependency so that ogUrl is updated every time we move to a different page.
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
const MyComponent = () => {
const router = useRouter();
const [ogUrl, setOgUrl] = useState("");
useEffect(() => {
const host = window.location.host;
const baseUrl = `https://${host}`;
setOgUrl(`${baseUrl}${router.pathname}`);
}, [router.pathname]);
return <div></div>
}
You need to ensure your access to window.location.hostname happens on the client-side only, and not during server-side rendering (where window does not exist). You can achieve that by moving it to a useEffect callback in your component.
function Component() {
useEffect(() => {
console.log(window.location.hostname)
console.log(window.location.href) // Logs `http://localhost:3000/blog/incididunt-ut-lobare-et-dolore`
}, [])
// Remaining code of the component
}
req.headers are Symbols and not Objects, so to get value, you use the get method
const host = req.headers.get("host"); // stackoverflow.com
AFAIK there are two ways of doing this:
Next JS provides us with the useRouter hook, first you have to import it in your component, then, to use the router object, you just have to declare it. For example:
const router = useRouter();
console.log(router.pathname);
const {pathname} = router; <---- To access the pathname directly.
Besides this, as #Xairoo said before, if you want to use the window object, you have to check if window !== 'undefined' to avoid errors. The window not defined error happens because Next JS use NodeJS to render the app and the window object is not defined in Node JS.
You can find a more detailed explanation in this link.
none oh the answers above solved the problem and this is the solution i figured it out :
function return_url(context) {
if (process.env.NODE_ENV === "production") {
// if you are hosting a http website use http instead of https
return `https://${context.req.rawHeaders[1]}`;
} else if (process.env.NODE_ENV !== "production") {
return "http://localhost:3000";
}
}
and on the getServerSideProps or getStaticProps functions you use
export async function getServerSideProps(context) {
let url = return_url(context);
const data = await fetch(`${url}/yourEndPoint`).then((res) => res.json());
return {
props: {
data: data,
},
};
}
Using a middleware.js file that you add to the root of your project can give you access to the host name and provide a lot of flexibility to perform actions based on it if needed.
https://nextjs.org/docs/advanced-features/middleware
// Example: redirecting a domain to a subdomain
import { NextResponse } from "next/server";
// This function can be marked `async` if using `await` inside
export function middleware(request) {
// Currently there is no main site so we redirect to the subdomain.
const host = request.headers.get("Host");
if (
process.env.NODE_ENV === "production" &&
host.startsWith("mydomain.com")
) {
return NextResponse.redirect(new URL("https://mysubdomain.mydomain.com"));
} else if (
process.env.NODE_ENV === "staging" &&
host.startsWith("staging.mydomain.com")
) {
return NextResponse.redirect(
new URL("https://mysubdomain-staging.mydomain.com")
);
}
}
in Next.js you can do like this,
by useEffect to get window.location.origin in client side,
and set it to state.
work fine in :
{
"next": "12.1.6",
"react": "18.1.0",
}
const Home: NextPage = () => {
const { asPath, query } = useRouter();
const [loading, setLoading] = useState(false);
const [loginCallBackURL, setLoginCallBackURL] = useState("");
useEffect(() => {
setLoginCallBackURL(
`${window.location.origin}/${query.redirect ? query.redirect : "user"}`,
);
}, []);
// if you do something like this, it can't get loginCallBackURL
// const loginCallBackURL = useMemo(() => {
// if (typeof window !== 'undefined') {
// return `${window.location.origin}/${
// query.redirect ? query.redirect : "user"
// }`;
// }
// return asPath;
// }, [asPath, query]);
return (
<div>
<Button
variant="contained"
href={queryString.stringifyUrl({
url: `${publicRuntimeConfig.API_HOST}/auth/google/login`,
query: {
callbackURL: loginCallBackURL,
},
})}
>
Sign in with google
</Button>
</div>
);
};
export default Home;
We can get current url like this:
import { useRouter } from 'next/router';
const router = useRouter();
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
const address_url = origin+router.asPath;
the problem
I created a Shopify node.js app using the Shopify CLI and I want to display a simple bar under the header using a script tag. I used the script tag API to add a script tag
"script_tags": [
{
"id": 174240039086,
"src": "https://xxxxx.ngrok.io/script_tag",
}
]
And I also added a <div id="script-app"></div> into the theme, under the header.
Here is my script_tag.js file, located in /pages/script_tag.js
import ReactDOM from 'react-dom';
class TestScriptTag extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<p>this is a bar</p>
</div>
);
}
}
ReactDOM.render(<TestScriptTag />, document.getElementById('script-app'));
export default TestScriptTag;
Lastly, here is my server.js (most of it is what came with the CLI):
import "#babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "#shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "#shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import { flushSync } from "react-dom";
const fs = require('fs');
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8083;
const dev = process.env.NODE_ENV !== "production";
const app = next({
dev,
});
const handle = app.getRequestHandler();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: false,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
createShopifyAuth({
async afterAuth(ctx) {
console.log("here")
// Access token and shop available in ctx.state.shopify
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
webhookHandler: async (topic, shop, body) =>
delete ACTIVE_SHOPIFY_SHOPS[shop],
});
if (!response.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to app with shop parameter upon auth
ctx.redirect(`/?shop=${shop}&host=${host}`);
},
})
);
const handleRequest = async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
};
router.get("/", async (ctx) => {
const shop = ctx.query.shop;
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
router.get("/script_tag", (ctx) => {
handleRequest(ctx);
});
router.get("(/_next/static/.*)", handleRequest); // Static content is clear
router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
I am getting the error: document not defined.
What I've tried
I thought this is due to server side rendering, so I thought I could get around it by doing this:
if (typeof window !== "undefined") {
ReactDOM.render(<TestScriptTag />, document.getElementById('script-app'));
}
But still nothing renders and I get this when I inspect the shop page.
I've also tried changing the routing to this:
router.get("/script_tag", (ctx) => {
ctx.type = "module";
ctx.body = fs.createReadStream('./pages/script_tag.js')
});
But then I get an error about the import statement in script_tag.js - SyntaxError: Unexpected identifier '{'. import call expects exactly one argument.
I'm not sure what the proper way is to serve the javascript file I want to inject into the header. I feel like I'm missing something stupid. Please help!!
I have this problem in Next.js. I call the API in getInitialProps and pass the params to my components then it works when I use it in mu project. But when I want to build it gives me error that the params are undefined.
This is how I call my API and pass it to component:
import BlogItem from './blogItem'
import axios from 'axios'
import { withRouter } from 'next/router'
import { APIURL, replaceAll } from '../../../components/config'
const Post = (props) => {
return (
<BlogItem
services ={props.services }
/>
)
}
Post.getInitialProps = async ({ query }) => {
const id = query.id
const urlTitle = encodeURI(query.title)
const services = null;
try {
const response = await axios.get(`${APIURL}getservice`);
services = response.data.response.posts;
} catch (err) {
console.error(err)
}
return {
services
}
}
export default withRouter(Post)
It seems that you defined services as a const that contain null! the whole code seems fine. Try changing const to let:
Post.getInitialProps = async ({ query }) => {
const id = query.id
const urlTitle = encodeURI(query.title)
let services = null; //this line
try {
const response = await axios.get(`${APIURL}getservice`);
services = response.data.response.posts;
} catch (err) {
console.error(err)
}
console.log(services) //you should have the data right here
return {services}
}
Example :
Look at this example. Run it and navigate to /about page :
You need to make sure that your url params are passed into the querystring on the server, In your case, something like this:
server.get('/page/:uid/:id', (req, res) => {
const mergedQuery = Object.assign({}, req.query, req.params)
return app.render(req, res, '/page', mergedQuery);
})
Then your getInitialProps can get them straight from the query, regardless of where it loads.
From nuxt auth website I saw this:
setUserToken(token)
Returns: Promise
Set the auth token and fetch the user using the new token and current strategy.
TIP: This function can properly set the user after registration
this.$auth.setUserToken(token)
.then(() => this.$toast.success('User set!'))
Tried to use it and it said method is undefined, looked up in the source files and none of methods are like this one.
I am not very good with this but, how would I set user and token with nuxt/auth module after registration or anything but login/loginWith?
If there is no option for that why is it there on documentation?
I would also need to know if I need to create custom auth do I need to use both cookies and localstorage or just one of them?
It says that cookies are used for server side and storage for client side.
Can I use just cookies and on nuxtServerInit get cookie for token and set token and user data fetched by api within vuex store? Then use it from there if it is needed?
Nuxt/auth module hurt my brain so long and today I created custom module:
First I have this store structure:
store/
-- index.js
-- mutations.js
-- actions.js
-- state.js
-- getters.js
middleware/
-- redirectIfAuth.js
-- redirectIfNotAuth.js
layouts/
default.vue -> has redirectIfNotAuth.js
guest.vue -> has redirectIfAuth.js
pages/
-- login/
---- index.vue -> uses guest.vue as layout
-- dashboard/
----- index.vue -> uses default.vue as layout without declaration
Inside Index.js I have:
import state from './state'
import * as actions from './actions'
import * as mutations from './mutations'
import * as getters from './getters'
export default {
state,
getters,
mutations,
actions,
modules: {}
}
Inside State.js I have:
export default () => ({
user: null,
token: null,
headers: null
})
Inside Actions.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
// importing server based cookie library
export async function nuxtServerInit ({ commit }, { req, res }) {
// If we have any axios requests we need to add async/await
// And since this works on server mode, we don't need to check is it server
let token = null
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
// If we have token within cookies we get user data from api and we pass Autorization headers with token
if (token !== null && token !== false) {
await axios.get('/api/auth/me', {
headers: {
'Authorization': token
}
}).then((response) => {
// If we get user data we set it to store
commit('setUser', response.data.data)
commit('setToken', token)
commit('setHeaders', token)
}).catch((error) => {
// If we get error, we should logout user by removing data within cookies and store
// Additionally you can create specific code error on backend to check if token is expired or invalid
// and then check for status code and then remove data
commit('setUser', null)
commit('setToken', null)
res.setHeader('Set-Cookie', [`authToken=false; expires=Thu, 01 Jan 1970 00:00:00 GMT`])
// This is only way I found useful for removing cookies from node server
console.warn(error)
})
}
}
Inside Mutations.js I have:
export const setToken = (state, payload) => state.token = payload
export const setUser = (state, payload) => state.user = payload
export const setHeaders = (state, payload) => {
state.headers = {
headers: {
'Authorization': payload
}
}
}
Inside Getters.js I have:
export const getUser = (state) => state.user
export const getToken = (state) => state.token
export const getHeaders = (state) => state.headers
Second I created two middlewares and it seems like nuxt middlewares work on both server and client sides, so I needed to require both libraries for server and client side Then I checked which side it is and then try to get token for further investigations If you include and don't check for server and client but use one of them, your templates wont render but show undefined errors for req on client instead and on server it wont show anything.
Inside redirectIfAuth.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
const Cookie = process.client ? require('js-cookie') : undefined
export default function ({ app, redirect, req }) {
let token = null
if (process.server) {
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
} else if (process.client) {
token = Cookie.get('authToken')
}
if (token && token !== false) {
app.store.commit('setToken', token)
app.store.commit('setHeaders', token)
if (app.store.state.user) {
if (app.store.state.user.roles.includes('customer')) {
return redirect({
name: 'customer-slug',
params: { slug: app.store.state.user.username }
})
} else if (app.store.state.user.roles.includes('admin')) {
return redirect({
name: 'dashboard'
})
} else {
return redirect({
name: 'index'
})
}
} else {
return redirect({
name: 'index'
})
}
}
}
Inside redirectIfNotAuth.js I have:
const cookieparser = process.server ? require('cookieparser') : undefined
const Cookie = process.client ? require('js-cookie') : undefined
export default function ({ app, redirect, route, req }) {
let token = null
if (process.server) {
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie)
try {
token = parsed.authToken
} catch (e) {
console.log(e)
}
}
} else if (process.client) {
token = Cookie.get('authToken')
}
if (token === null || token === false) {
return redirect({
name: 'login',
query: {
redirect: route.fullPath
}
})
}
}
Now we use these middlewares within pages or layouts as:
export default {
middleware: ['redirectIfAuth']
}
Or
export default {
middleware: ['redirectIfNotAuth']
}
Login:
async login () {
if (this.form.email !== '' && this.form.password !== '') {
await this.$axios.post('/api/auth/login', this.form).then((response) => {
this.$store.commit('setUser', response.data.data)
this.$store.commit('setToken', 'Bearer ' + response.data.meta.access_token)
this.$store.commit('setHeaders', 'Bearer ' + response.data.meta.access_token)
Cookie.set('authToken', 'Bearer ' + response.data.meta.access_token, { expires: 365 })
// Cookie.set('authUser', response.data.data, { expires: 365 }) if you need user data within cookies
if (this.$route.query.redirect) {
this.$router.push(this.$route.query.redirect)
}
this.$router.push('/')
})
}
}
Logout:
async logout () {
await this.$axios.post('/api/auth/logout', {}, this.headers)
// Cookie.remove('authUser') if exists
Cookie.remove('authToken')
this.$router.push('/')
}
I hope this helps someone or someone get idea from this to make something else. I had million problems with official nuxt auth and only this helped me sort things out...