Next-Auth has been working great for me until i decided to create a custom signIn page.
I have google login working perfectly and i can see lots going on in the debugger around token generation and session creation with my adapter.
However, when I try to sign in using email verification it just clears the email from the input box and then does nothing. The Form is straight out of the next auth docs.
I have triple checked the settings for my sendgrid apikey and they are all set correctly in my .env.local file.
"/pages/api/auth/[...nextauth].js"
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import EmailProvider from "next-auth/providers/email"
import { FirestoreAdapter } from "#next-auth/firebase-adapter"
const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
}
},
from: process.env.EMAIL_FROM
}),
],
session: {
jwt: true,
},
adapter: FirestoreAdapter({
apiKey: process.env.FIREBASE_API_KEY,
appId: process.env.FIREBASE_APP_ID,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.FIREBASE_DATABASE_URL,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID
}),
theme: ({
colorScheme: "dark",
}),
pages: {
signIn: "/auth/signin",
newUser: "/auth/signup",
},
debug: true,
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true
},
async redirect({ url, baseUrl }) {
return baseUrl
},
async session({ session, user, token }) {
return session
},
async jwt({ token, user, account, profile, isNewUser }) {
return token
}
}
}
export default NextAuth(authOptions)
Here is my custom page...
"/pages/auth/signin.js"
import React from 'react'
import {getProviders, signIn, getCsrfToken} from 'next-auth/react'
import styles from '../../styles/signin.module.css'
import Logo from '../../components/Logo'
export default function SignIn ({ csrfToken, providers }) {
return (
<div>
<div className={styles.content}>
<div className={styles.cardWrapper}>
<Logo className={styles.logo}/>
<div className={styles.cardContent}>
<div className={styles.emailForm}>
<form method="post" action="/api/auth/signin/email">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<input type="text" id="email" name="email" placeholder="Email" />
<button type="submit">Sign in with Email</button>
</form>
</div>
<hr />
{Object.values(providers).map((provider) => {
if (provider.name === "Email") {
return
}
return (
<div key={provider.name}>
<button onClick={() => signIn(provider.id)}>
Sign in with {provider.name}
</button>
</div>
)
})}
</div>
</div>
</div>
</div>
)
}
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context)
const providers = await getProviders(context)
return {
props: { csrfToken },
props: { providers },
}
}
EDIT: Installed Dependencies
"next": "^12.2.5",
"next-auth": "^4.10.3",
"nodemailer": "^6.7.8",
"react": "18.2.0",
"react-dom": "18.2.0",
"swiper": "^8.3.2",
"swr": "^1.3.0"
"#next-auth/firebase-adapter": "^1.0.1",
"#types/node": "^18.7.13",
"core-js": "^3.25.0",
"firebase": "^9.9.3",
"fs": "^0.0.1-security",
EDIT: ENV SETTINGS
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=REDACTED SECRET
EMAIL_SERVER_HOST=smtp.sendgrid.net
EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER=apikey
EMAIL_SERVER_PASSWORD=REDACTED (SET AS THE API KEY PROVIDED BY SENDGRID)
EMAIL_FROM=REDACTED (SET AS VERIFIED SINGLE USER EMAIL ADDRESS IN SENDGRID)
Also set are the following...
FIREBASE_API_KEY=REDACTED
FIREBASE_AUTH_DOMAIN=REDACTED
FIREBASE_PROJECT_ID=REDACTED
FIREBASE_STORAGE_BUCKET=REDACTED
FIREBASE_MESSAGING_SENDER_ID=REDACTED
FIREBASE_APP_ID=REDACTED
FIREBASE_DATABASE_URL=REDACTED
EDIT: "/pages/_app.js" - Added main app just in case the problem is being caused by the way I have wrapped my component.
import React from 'react'
import App from 'next/app'
import { SessionProvider } from "next-auth/react"
import Layout from "../components/Layout.js"
import AccountSettingsLayout from '../components/AccountSettingsLayout'
import { SWRConfig } from 'swr'
import '../styles/globals.css'
class MyApp extends App {
render() {
const { Component, router, pageProps: { session, ...pageProps }} =
this.props
if (router.pathname.startsWith('/auth/')) {
return (
<React.StrictMode>
<SessionProvider session={session}>
<SWRConfig>
<AccountSettingsLayout>
<Component {...pageProps} />
</AccountSettingsLayout>
</SWRConfig>
</SessionProvider>
</React.StrictMode>
)
}
return (
<React.StrictMode>
<SessionProvider session={session}>
<SWRConfig>
<Layout>
<Component {...pageProps} />
</Layout>
</SWRConfig>
</SessionProvider>
</React.StrictMode>
)
}}
export default MyApp
UPDATE: when the email address is entered and the submit is clicked i get the following change in the URL.
starting URL = http://localhost:3000/auth/signin
after submit= http://localhost:3000/auth/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000
UPDATED 30.08.2022 -
I can see in terminal the following...
wait - compiling /_error (client and server)...
However the event compiles straight after without any error being displayed.
I read in the next.js docs that the payload for email must 2 entries, the CSRF token and the email address. I can see in the browser tools that the CSRF token is generated. I can also see in the network tab that the payload shows the email address but the CSRF Token is blank on submission.
Thanks in advance for any help on this one. Go easy on me as I am a new coder!
Ok so the problem was with my getServerSideProps...
BEFORE:
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context)
const providers = await getProviders(context)
return {
props: { csrfToken },
props: { providers },
}
}
AFTER:
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context)
const providers = await getProviders(context)
return {
props: { csrfToken, providers },
}
}
I noticed that the csrfToken was not being passed into the email submit payload.
For others in future...using chrome devtools if you inspect your page prior to clicking submit then you can click on network and check the payload and header.
Related
Please find below the code of my (very simple, for demonstration purposes) custom login page. I am using getSession inside getServerSideProps to determine wheter there is already a session with a user. If that is the case, I redirect to the "root page". If not, I "hydrate" my page with the currently available "providers" as "props".
Is my approach valid? Or is there anything "more best-practice" I could do? And, specifically, is it ok to use getSession inside getServerSideProps in this way?
import { getProviders, getSession, signIn } from 'next-auth/react';
import type { BuiltInProviderType } from 'next-auth/providers';
import type { ClientSafeProvider, LiteralUnion } from 'next-auth/react';
import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
import { Session } from 'next-auth';
interface Properties {
providers: Record<
LiteralUnion<BuiltInProviderType, string>,
ClientSafeProvider
> | null;
}
export default function SignIn({ providers }: Properties) {
return (
<>
{providers &&
Object.values(providers).map((provider) => (
<div key={provider.name}>
<button
onClick={() => signIn(provider.id, { callbackUrl: '/test' })}
>
Sign in with {provider.name}
</button>
</div>
))}
</>
);
}
export const getServerSideProps: GetServerSideProps = async (
context: GetServerSidePropsContext
) => {
const session: Session | null = await getSession({ req: context.req });
if (session && session.user) {
console.log(
'Since there is already an active session with a user you will be redirected!'
);
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
return { props: { providers: await getProviders() } };
};
I'm fairly new to Vue and it's my first time using Pinia. I'm following this guide to set up Firebase, Pinia and Axios. The app I'm building uses FirebaseUI to sign a user in, via an email link - this all happens in the LoginPage component below:
(Please ignore all incorrectly types variables/functions - I'm just trying to get this working in the first place)
<script setup lang="ts">
import { onMounted } from "vue";
import { EmailAuthProvider } from "firebase/auth";
import { auth } from "firebaseui";
import { auth as firebaseAuth } from "../firebase/config";
import { useUserStore } from "../stores/user"
onMounted(async () => {
const uiConfig: auth.Config = {
signInSuccessUrl: "/",
signInOptions: [
{
provider: EmailAuthProvider.PROVIDER_ID,
signInMethod: EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD,
forceSameDevice: true,
},
],
callbacks: {
signInSuccessWithAuthResult: function (authResult) {
const store = useUserStore();
store.user = authResult;
return true;
},
},
};
const ui = new auth.AuthUI(firebaseAuth);
ui.start("#firebaseui-auth-container", uiConfig);
});
</script>
<template>
<div id="firebaseui-auth-container"></div>
</template>
When the user successfully signs in, the app updates the Pinia store user object, with the AuthResult return object from the signInSuccessWithAuthResult function. When debugger, I can see that the object being stored looks like the following:
{
additionalUserInfo: {...}
operationType: "signIn"
user: {
accessToken: "eyJhbGciOiJSUzI1N..."
auth: {...}
displayName: null
...
}
}
I.e. the accessToken is being stored. The user store is below:
import { defineStore } from 'pinia'
export const useUserStore = defineStore("userStore", {
state: () => ({
user: null as any
}),
getters: {
getUser(state) {
return state.user
}
}
})
In the app I have set up an axios interceptor, that appends the accessToken to any Axios request made by the app:
axiosInstance.interceptors.request.use((config) => {
const userStore = useUserStore();
if (userStore) {
debugger;
// accessToken is undefined
config.headers.Authorization = 'Bearer ' + userStore.user.user.accessToken;
}
return config;
});
When attempting the retrieve the accessToken from the user store at this point, it's gone. Most (if not all) of the other properties from the user object still exist, but not the access token, therefore I'm pretty sure I'm using the store correctly:
{
additionalUserInfo: {...}
credential: null
operationType: "signIn"
user: {
// accessToken is gone
apiKey: "..."
appName: "[DEFAULT]"
email: "..."
emailVerified: true
....
}
}
Can anybody explain where I'm going wrong with this, and why the accessToken is being removed from the store? It looks to me as though I'm using the Pinia store correctly, and I'm pretty sure that the interceptor is also correct. However it's likely that I'm going about storing the access token in the wrong way. I'd appreciate any help/advice about how to setup Firebase authentication correctly with Vue.
Edited to include value of the user store when debugging inside the interceptor.
It looks like accessToken might be in userStore.user.user.accessToken?
Im just finishing the same battle that you are in... IMO there are many ways that this setup can be configured... This is similar to why you might use callbacks in one place, and async await in another it depends on your project structure.
Heres a simple example that might help you clarify it.
first
create a firebase file to hold the config put this where ever your organization habits tells you to put it. Just remember so we can use it later.
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
//initialize firebase auth
export const auth = getAuth(app);
Second - userStore
The user store does the legwork. We will use the actions when we want to interact with userauth from our ui.
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import { auth } from "../firebase"; // the file we made above
import router from "../router";
export const useUserStore = defineStore("userStore", {
state: () => ({
userData: null,
loadingUser: false,
loadingSession: false,
}),
actions: {
async registerUser(email, password) {
this.loadingUser = true;
try {
const { user } = await createUserWithEmailAndPassword(
auth,
email,
password
);
this.userData = { email: user.email, uid: user.uid };
router.push("/");
} catch (error) {
console.log(error);
} finally {
this.loadingUser = false;
}
},
async loginUser(email, password) {
this.loadingUser = true;
try {
const { user } = await signInWithEmailAndPassword(
auth,
email,
password
);
this.userData = { email: user.email, uid: user.uid };
router.push("/");
} catch (error) {
console.log(error);
} finally {
this.loadingUser = false;
}
},
async logOutUser() {
try {
await signOut(auth);
this.userData = null;
router.push("/login");
} catch (error) {
console.log(error);
}
},
currentUser() {
return new Promise((resolve, reject) => {
const unsuscribe = onAuthStateChanged(
auth,
(user) => {
if (user) {
this.userData = { email: user.email, password: user.password };
} else {
this.userData = null;
}
resolve(user);
},
(e) => reject(e)
);
unsuscribe();
});
},
},
});
*** step3 setup the login / reg components in vue. ***
<div>
<form #submit.prevent="login">
<label>
Email:
<input type="email" v-model="email" required />
</label>
<br />
<label>
Password:
<input type="password" v-model="password" required />
</label>
<br />
<button type="submit">Login</button>
</form>
</div>
</template>
<script>
import { useUserStore } from "../stores/user";
export default {
data() {
return {
email: "",
password: "",
};
},
methods: {
async login() {
try {
await this.userStore.loginUser(this.email, this.password); //
} catch (error) {
console.error(error);
}
},
},
// because of below setup you can access this.userStore() singleton
setup() {
const userStore = useUserStore();
return {
userStore,
};
},
};
</script>
register is going to be simailar
<div>
<form #submit.prevent="register">
<label>
Email:
<input type="email" v-model="email" required />
</label>
<br />
<label>
Password:
<input type="password" v-model="password" required />
</label>
<br />
<button type="submit">Register</button>
</form>
</div>
</template>
<script>
import { useUserStore } from "../stores/user";
export default {
data() {
return {
email: "",
password: "",
};
},
methods: {
async register() {
try {
await this.userStore.registerUser(this.email, this.password);
} catch (error) {
console.error(error);
}
},
},
setup() {
const userStore = useUserStore();
return {
userStore,
};
},
};
</script>
now whenever you want to access the user it is in userStore.userData
if you dont have the userStore up yet just use the useUserStore() method and access it the same way you do from the setup in login / register view
Whenever I try to navigate to my sign-in page, it redirects to /api/auth/error on the vercel deployment. Locally, it navigates and works as expected.
From inspecting the network tab the first network request to fail is to /api/auth/providers
Not exactly sure where it's going wrong.
/pages/api/auth/[...nextauth].ts
import { PrismaAdapter } from '#next-auth/prisma-adapter';
import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
import GoogleProvider from 'next-auth/providers/google';
import LinkedInProvider from 'next-auth/providers/linkedin';
import prisma from 'utils/prismaClient';
export default NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID as string,
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}),
LinkedInProvider({
clientId: process.env.LINKEDIN_CLIENT_ID as string,
clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string,
}),
],
adapter: PrismaAdapter(prisma),
pages: {
signIn: '/sign-in',
error: '/sign-in',
},
});
/pages/sign-in.tsx:
type Props = {
providers: Provider[];
};
const SignIn: NextPage<Props> = ({ providers }) => {
const router = useRouter();
const [authCallbackUrl, setAuthCallbackUrl] = useState<string | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
useEffect(() => {
if (!router.isReady) return;
setAuthCallbackUrl(('/' + router.query.callbackUrl) as string);
setError(router.query.error as string);
}, [router.isReady, router.query.callbackUrl, router.query.error]);
return (
<div>
<div>
{Object.values(providers).map((provider) => (
<button
key={provider.name}
type="button"
onClick={async () => {
await signIn(provider.id, {
callbackUrl: authCallbackUrl,
});
}}
>
{getSvgByProvider(provider.name)}
Sign in with {provider.name}{' '}
</button>
))}
</div>
{error && <SignInError error={error as keyof typeof SIGNIN_ERRORS} />}
</div>
);
};
export async function getServerSideProps() {
const providers = await getProviders();
return {
props: { providers },
};
}
export default SignIn;
Github Provider Callback Urls:
${VERCEL_URL}/api/auth/callback/github
I imagine there is nothing with my OAuth setup because it works locally? but not sure.
I've tried deploying with and without NEXTAUTH_URL in the env variables for Vercel but it has no effect as expected. According to documentation
When deploying here, you do not need to explicitly set the NEXTAUTH_URL environment variable.
Any idea what is going wrong? It works locally but when I deploy it, as soon as I call signIn(); from the homepage it navigates to api/auth/error
Edit: After console.logging providers inside the Sign-in page, it returns null. Any idea why that is the case?
Edit 2: Seems this is something to do with deploying it on Vercel? I deployed the same application on Netlify with NEXTAUTH_URL env variable defined and it works
DESCRIPTION:
I'm facing while implementing custom login form in my nextjs site using next-auth.
It keeps on redirecting to "https://localhost:3000/api/auth/signin?csrf=true"
SERVER SIDE CODE WRITTEN IN "[...nextauth.js]" FILE INSIDE "pages/api/auth/" folder.
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
const providers = [
CredentialsProvider({
id: 'credentials',
name: 'credentials',
authorize: async (credentials) => {
console.log("AUTHORIZING...",credentials,req);
const res = await fetch(process.env.apiURL+"api/users/authenticate", {
method: 'POST',
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }
});
const user = await res.json();
if (res.ok && user) {
return user;
}
return null;
}
}),
];
const callbacks = {
async signIn({ user, account, profile, email, credentials }) {
console.log("IN CALLBACK HERE!!!");
const isAllowedToSignIn = true
if (isAllowedToSignIn) {
return true
} else {
return false
}
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session(session, token) {
console.log("IN THE CALLBACK SESSION NOW");
session.accessToken = token.accessToken
return session
}
}
const options = {
providers,
callbacks,
secret: process.env.JWT_SECRET,
pages: {
signIn: '/my-account',
signUp: '/signup',
error: '/my-account'
},
debug: true
}
export default (req, res) => NextAuth(req, res, options);
MY CUSTOM LOGIN FORM'S SUBMIT BUTTON ACTION:
const res = await signIn('credentials', { redirect: false, username: user.username, passw: user.passw, callbackUrl: `${window.location.origin}` });
console.log(res);
if (res?.error) {
console.error(res.error);
}
if (res.url) router.push(res.url);
ENVIRONMENT VARIABLES
NEXTAUTH_URL=https://localhost:3000/api/auth
NEXTAUTH_SECRET=PETSFINEST
ERROR IS
the Authorize function of Credentials Provider is not at all being called.. as soon as I press SIGNIN button from my custom login form, it simply redirects to "https://localhost:3000/api/auth/signin?csrf=true" URL and thats it.
Please help guys.. what am I doing wrong here? There's no other details available anywhere, and I've followed the documentation provided on next-auth site.
I ran into this same issue. After banging my head against the wall for ages, it ends up https was being used because I was running it with Vercel...
So rather than using vercel dev I switched it to npx next dev and it resolved the issue for me.
Hey try changing to http on the NEXTAUTH_URL env variable
So change this:
NEXTAUTH_URL=https://localhost:3000/api/auth
into
NEXTAUTH_URL=http://localhost:3000/api/auth
Open the browser inspect tools and check the network tab. Pay attention to the payload on the submit. You should have csrfToken showing a value. Check that email, password and csrf are present in the payload. This helped me solve my issue similar to this when implementing passwordless email form. Here is my custom signin...
import React from 'react'
import {getProviders, signIn} from 'next-auth/react'
import { getCsrfToken } from "next-auth/react"
import styles from '../../styles/signin.module.css'
import Logo from '../../components/Logo'
import Link from 'next/link'
import { GoogleLogin } from '../../components/GoogleLogin'
export default function SignIn ({ csrfToken, providers }) {
console.log(providers.google.signinUrl);
return (
<div className={styles.content}>
<div className={styles.cardWrapper}>
<Logo className={styles.logo}/>
<div className={styles.cardContent}>
<form method="post" action="/api/auth/signin/email">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<input type="email" id="email" name="email" placeholder='Email..' />
<button className={styles.primaryBtn} type="submit">Sign in with Email</button>
</form>
<p className={styles.seperator}> Or </p>
<form method="post" action="/api/auth/signin/google">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<button className={styles.primaryBtn} type="submit">Sign in with Google</button>
</form>
</div>
</div>
</div>
)
}
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context)
const providers = await getProviders(context)
return {
props: { csrfToken, providers },
}
}
I think you can do something similar for credentials as long as you have set it up correctly as per docs in [...nextauth].js.
The code above can be put into seperate components to "Reactify" the code.
Hope this helps.
I've been having the same exact issue and banging my head against the wall for a few hours trying to debug this. The problem for me was that my SMTP server string was still using the 'smtp.example.com' from the docs instead of (in my case) 'smtp.sendgrid.net'.
EMAIL_SERVER="smtp://<user>:<password>#smtp.example.com:587"
I'm using Firebase, FirebaseUI, Vue, vue router, and vuex.
I have a home view ('/') that shows a "Log In" button. At this point, the vuex state has a user variable set to null. When I click on it, it takes me to my login view ('/login'). Suspicious thing: Looking at Vue DevTools, home remains the active view.
I use FirebaseUI to handle my logging in stuff. Once successfully logged in, my site redirects to a console view ('/console') which has a "Log Out" button, and the user variable in vuex is set to an object. Suspicious thing: Looking at Vue DevTools, login is now the active view.
Clicking the "Log Out" button still keeps me in the '/console/ vue. Vuex has a user variable set to null.
Refreshing the page automatically redirects me to the '/login' view.
I'm wondering if the fact that the active view isn't correct is the root of this issue. I found other people with the same problem saying they weren't using the webpack correctly by importing their App.vue file as a js, but I'm importing it as a .vue file in my main.js file.
I'm also suspicious of the FirebaseUI uiConfig in my Login.vue file. I have to set signInSuccessUrl: '#/console', (note the #). If I don't have the #, it redirects me to home (url = http://localhost:8081/console#/).
App.Vue
<template>
<v-app>
<v-content>
<router-view></router-view>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'App',
methods: {
setUser: function() {
this.$store.dispatch('setUser');
}
},
data: () => ({
//
}),
created () {
// when the app is created run the set user method
// this uses Vuex to check if a user is signed in
// check out mutations in the store/index.js file
this.setUser();
},
};
</script>
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify'
import { store } from './store/index';
// Firebase App (the core Firebase SDK) is always required and
// must be listed before other Firebase SDKs
import firebase from "firebase/app";
// Add the Firebase services that you want to use
import "firebase/auth";
import "firebase/firestore";
var firebaseConfig = {
// I'm editing this part out
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
// Check before each page load whether the page requires authentication/
// if it does check whether the user is signed into the web app or
// redirect to the sign-in page to enable them to sign-in
router.beforeEach((to, from, next) => {
const currentUser = firebase.auth().currentUser;
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
if (requiresAuth && !currentUser) {
next('/login');
} else if (requiresAuth && currentUser) {
next();
} else {
next();
}
});
// Wrap the vue instance in a Firebase onAuthStateChanged method
// This stops the execution of the navigation guard 'beforeEach'
// method until the Firebase initialization ends
firebase.auth().onAuthStateChanged(function (user) {
user;
new Vue({
el: '#app',
store,
router,
vuetify,
render: h => h(App)
});
});
router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: () => import('../views/About.vue')
},
{
path: '/tryit',
name: 'tryit',
component: () => import('../views/TryIt.vue')
},
{
path: '/pricing',
name: 'pricing',
component: () => import('../views/Pricing.vue')
},
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue')
},
{
path: '/console',
name: 'console',
component: () => import('../views/Console.vue'),
meta: { requiresAuth: true }
}
]
const router = new VueRouter({
routes
})
export default router
store\index.js
import Vue from 'vue';
import Vuex from 'vuex';
import Firebase from 'firebase';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
user: null
},
getters: {
getUser: state => {
return state.user;
}
},
mutations: {
setUser: state => {
state.user = Firebase.auth().currentUser;
},
logoutUser: state => {
state.user = null;
}
},
actions: {
setUser: context => {
context.commit('setUser');
},
logoutUser: context => {
context.commit('logoutUser');
}
}
});
Login.vue
<template>
<div class="login">
<NavbarAnon />
<!-- The surrounding HTML is left untouched by FirebaseUI.
Your app may use that space for branding, controls and other customizations.-->
<h1>Welcome to My Awesome App</h1>
<div id="firebaseui-auth-container"></div>
<div id="loader">Loading...</div>
</div>
</template>
<script>
// # is an alias to /src
import NavbarAnon from '#/components/layout/NavbarAnon';
import 'firebaseui/dist/firebaseui.css';
// Firebase App (the core Firebase SDK) is always required and
// must be listed before other Firebase SDKs
var firebase = require("firebase/app");
// Add the Firebase products that you want to use
require("firebase/auth");
require("firebase/firestore");
var firebaseui = require('firebaseui');
export default {
name: 'login',
components: {
NavbarAnon
},
mounted() {
// Initialize the FirebaseUI Widget using Firebase.
let ui = firebaseui.auth.AuthUI.getInstance();
if (!ui) {
ui = new firebaseui.auth.AuthUI(firebase.auth());
}
var uiConfig = {
callbacks: {
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
// User successfully signed in.
// Return type determines whether we continue the redirect automatically
// or whether we leave that to developer to handle.
authResult + redirectUrl;
return true;
},
uiShown: function() {
// The widget is rendered.
// Hide the loader.
document.getElementById('loader').style.display = 'none';
}
},
// Will use popup for IDP Providers sign-in flow instead of the default, redirect.
signInFlow: 'popup',
signInSuccessUrl: '#/console',
signInOptions: [
// Leave the lines as is for the providers you want to offer your users.
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
firebase.auth.EmailAuthProvider.PROVIDER_ID
],
// Terms of service url.
tosUrl: '<your-tos-url>',
// Privacy policy url.
privacyPolicyUrl: '<your-privacy-policy-url>'
};
// The start method will wait until the DOM is loaded.
ui.start('#firebaseui-auth-container', uiConfig);
}
};
</script>
In the HeaderBar.vue (which has the logout button):
methods: {
signOutUser() {
//this.$emit("trying to sign out user");
firebase.auth().signOut().then(function() {
// Sign-out successful.
this.$store.dispatch('logoutUser');
this.$route.push('/');
}).catch(function(error) {
//this.$emit("signout error", error);
error;
// An error happened.
});
}
}