How to create a middleware for check role in Nuxtjs - javascript

I'm trying to create a middleware for check role of my users.
// middleware/is-admin.js
export default function (context) {
let user = context.store.getters['auth/user']
if ( user.role !== 'admin' ) {
return context.redirect('/errors/403')
}
}
In my .vue file, I'm putting this on:
middleware: [ 'is-admin' ]
It works.
Now, I'd like to check if the user also has another role. So, I create a new middleware:
// middleware/is-consultant.js
export default function (context) {
let user = context.store.getters['auth/user']
if ( user.role !== 'consultant' ) {
return context.redirect('/errors/403')
}
}
And in my .vue file:
middleware: [ 'is-admin', 'is-consultant' ]
Unfortunately, when I do that, if I visit the route with an administrator role, it does not work anymore.
Can you tell me how I can create a middleware that checks multiple roles with Nuxt.js?
Thank you!

The idea is that every page has its authority level. Then in middleware you can compare your current user authority level with the current page authority level, and if it's lower redirect the user. It's very elegant solution that was proposed by Nuxt.js creator. GitHub issue.
<template>
<h1>Only an admin can see this page</h1>
</template>
<script>
export default {
middleware: 'auth',
meta: {
auth: { authority: 2 }
}
}
</script>
Then in your middleware/auth.js:
export default ({ store, route, redirect }) => {
// Check if user is connected first
if (!store.getters['user/user'].isAuthenticated) return redirect('/login')
// Get authorizations for matched routes (with children routes too)
const authorizationLevels = route.meta.map((meta) => {
if (meta.auth && typeof meta.auth.authority !== 'undefined')
return meta.auth.authority
return 0
})
// Get highest authorization level
const highestAuthority = Math.max.apply(null, authorizationLevels)
if (store.getters['user/user'].details.general.authority < highestAuthority) {
return error({
statusCode: 401,
message: 'Du måste vara admin för att besöka denna sidan.'
})
}
}

You can use this feature in Nuxt
export default function ({ $auth, redirect }) {
if (!$auth.hasScope('admin')) {
return redirect('/')
}
}
The scope can be anything you want e.g Consultant, Editor etc.
Check the documentation
Updated
Since you are using Laravel
You can have a role column in your user table
e.g
$table->enum('role', ['subscriber', 'admin', 'editor', 'consultant', 'writer'])->default('subscriber');
Then create a API resource, check the documentation for more
To create a user resource, run this artisan
php artisan make:resource UserResource
Then in your resource, you can have something like this
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'gender' => $this->gender,
'country' => $this->country,
'avatar' => $this->avatar,
'role' => $this->role,
];
}
Then you can import it to your controller like this
use App\Http\Resources\UserResource;
You can get the resource like this
$userdata = new UserResource(User::find(auth()->user()->id));
return response()->json(array(
'user' => $userdata,
));
In Nuxt
To do authentication in Nuxt
Install nuxt auth and axios
Using YARN : yarn add #nuxtjs/auth #nuxtjs/axios
Or using NPM: npm install #nuxtjs/auth #nuxtjs/axios
Then register them in your nuxtconfig.js
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth',
],
In your nuxtconfig.js, add this also
axios: {
baseURL: 'http://127.0.0.1:8000/api'
},
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/login', method: 'post', propertyName: 'access_token' },
logout: { url: '/logout', method: 'post' },
user: { url: '/user', method: 'get', propertyName: false }
},
tokenRequired: true,
tokenType: 'Bearer',
globalToken: true
// autoFetchUser: true
}
}
}
The URL been the endpoints
Check Documentation for more
To restrict certain pages in Nuxt to Specific User.
> Create a middlweare e.g isadmin.js
Then add this
export default function ({ $auth, redirect }) {
if (!$auth.hasScope('admin')) {
return redirect('/')
}
}
Then go to the Page, add the middleware
export default {
middleware: 'isadmin'

Related

AWS-Amplify, How do I redirect to another URL after sign-in in angular?

I'm using the latest AWS-Amplify's authentication component. It can logged in successfully but after login I need to sent the route to another url which I can't able to achieve, it keeping the same url after logged in. BUT I need to set a custom url where it automatically redirect if a user login successfully.
Note : I'm not using aws-amplify-angular package I'm using these packages,
"#aws-amplify/ui-angular": "^2.4.4",
"aws-amplify": "^4.3.21",
Also I checked this import {AuthenticatorService} from '#aws-amplify/ui-angular'; service but here I didn't find any response with observable type, I think that's why I don't get any event or something instantly after user login successfully. I need to route imminently after a successful login. So I need an event so that I can do that .
My main.ts :
import { Amplify } from 'aws-amplify'
import awsmobile from './aws-exports'
Amplify.configure(awsmobile)
auth.component.html : [ there is no code in ts ]
<amplify-authenticator [signUpAttributes]="['email']"></amplify-authenticator>
& the routes setup like this,
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
canActivate: [AuthGuard]
},
{
path: 'auth',
component: AuthComponent
},
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
}
];
I didn't get any good solution with using this packages. Please help with this issue or am I missed something in my configuration .
I didn't want to use aws-amplify-angular package but I need to because there is no good solution out there, without this package I tried to use angular hooks because when change detection runs I can get authenticated & unauthenticated states from AuthenticatorService which came from import { AuthenticatorService } from '#aws-amplify/ui-angular'; but using hooks (AfterContentChecked, AfterViewChecked) it works sometimes,sometimes not & sometimes occurred errors.
[ I did't use both hooks at a time, I tried separately]
So, Finally I need to install aws-amplify-angular for using the AmplifyService's & then I do this changes bellow,
constructor(private router: Router, private amplifyService: AmplifyService) { }
ngOnInit() {
this.amplifyService.authStateChange$.subscribe(({state, user}) => {
if (state === 'signedIn') {
this.router.navigate(['/home'])
}
})
}
Don't forget to add AmplifyService to put in providers array in app.module.ts.
In the AuthenticatorService import of the latest version of #aws-amplify/ui-angular, you can subscribe and listen for authStatus which is authenticated, deauthenticated and configured, if you listen from ngOnInit or from the constructor you can wait for the necessary status to do redirection.
It also works for the signOut
constructor(public amplifyService: AuthenticatorService, public router: Router) {
this.amplifyService = amplifyService;
amplifyService.subscribe((data: any) => {
if (data.authStatus === "authenticated") {
this.router.navigate(['/home']);
};
})
}
ngOnInit(): void {
}
to make signOut you just have to use this function in your navbar
logOut() {
this.amplifyService.signOut()
this.router.navigate(['/login']);
}
This way you can maintain the latest version of aws-amplify packages and not get errors or failures from compiling outdated packages or packages that conflict with other aws packages.
import { Hub } from 'aws-amplify';
ngOnInit() {
// Used for listening to login events
Hub.listen('auth', ({ payload: { event, data } }) => {
if (event === 'signIn') {
const { signInUserSession = {} } = data;
const { accessToken = {} } = signInUserSession;
const { jwtToken, payload = {} } = accessToken;
const { 'cognito:groups': roles, username, exp } = payload;
localStorage.setItem('accessToken', jwtToken);
const payloadToSave = {
Role: roles[0],
Username: username,
exp,
};
localStorage.setItem('payload', JSON.stringify(payloadToSave));
this.zone.run(() => this.router.navigate(['/dashboard']));
} else {
console.log('Hide loader');
}
});
//currentAuthenticatedUser: when user comes to login page again
Auth.currentAuthenticatedUser()
.then(() => {
this.router.navigate(['/dashboard'], { replaceUrl: true });
})
.catch(err => {
console.log(err);
});
}

NextJS: Failed when fallback set to true

I am using vercel for NextJS and this is my setup in getStaticPaths
const paths = posts.map((post) => ({
params: { player: post.player, id: post.id },
}))
return { paths, fallback: true }
When I set the fallback to true, I have got this error in vercel:
21:55:01.736 info - Generating static pages (1752/1752)
21:55:01.736 > Build error occurred 21:55:01.739 Error: Export
encountered errors on following paths: 21:55:01.739
/clip/[player]/[id]
It is ok when fallback is set to false but I really like to set fallback set to true so that pages can be updated frequently. Any help will be greatly appreciated...
Inside your /clip/[player]/[id].js file, you need to handle the fallback state when that page is being requested on-demand.
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
https://nextjs.org/docs/basic-features/data-fetching#fallback-true
What I did was conditionally render my component. So, my component receives the object data and if I need to use a value from data, such as "title", I will do...
data?.title
Also, for my entire return component I will conditionally render it. For example...
{data !== undefined ? (
<div className ='main-content'>
<p> This is the content that I want rendered if data is defined </p>
</div>
) : (
<div className = 'fallback-content'>
<p> This shows if data == undefined </p>
</div>
)

Nuxt: Auth + Axios setup

I'm struggling with the configuration of auth + Axios.
I'm currently working on our social login (FB and google). It half works.
First of all, I have my Axios instance configured as a plugin. We do have two instances, one that we use for general API requests and another one that we will use for logged user requests.
plugins/axios.js
export default function({ $axios, redirect, app }) {
$axios.defaults.baseURL = process.env.baseUrl
$axios.defaults.headers = {
Authorization: `Bearer ${process.env.APIKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
lang: app.i18n.locale
}
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
if (code === 404) {
redirect('/404')
}
if (code === 500) {
redirect('/500')
}
})
}
plugins/auth-axios.js
export default function({ $axios }, inject) {
const authAxios = $axios.create()
// authAxios.setToken('123', 'Bearer')
inject('authAxios', authAxios)
}
The first client works perfectly in the whole app.
Now I'm configuring the auth plugin. When I log in through Facebook or google I need to take the response data of the logged user from the social network and send it to my API, expecting a user with the token as the response. This will be the user that I'll set in the auth plugin. Every time I log in with Facebook it appears to work well except when It arrives at the plugin code. $auth.loggedIn is always false.
I've made it work forcing $auth.fetchUser at the beginning of the code but it doesn't work well at all. It shows an Axios error when console.log($auth). This is my auth code:
plugins/auth.js
export default async function({ app: { $auth, $axios } }) {
$auth.fetchUser()
console.log($auth)
if (!$auth.loggedIn) {
return
}
const authStrategy = $auth.strategy.name
if (authStrategy === 'facebook' || authStrategy === 'google') {
if ($auth.user.google_id || $auth.user.fb_id) return
try {
const url = `/client/social`
var postData
if (authStrategy == 'facebook')
postData = {
name: $auth.user.name,
email: $auth.user.email,
fb_id: $auth.user.id,
avatar: $auth.user.picture.data.url,
birthday: $auth.user.birthday
}
else
postData = {
name: $auth.user.given_name,
surname: $auth.user.family_name,
email: $auth.user.email,
google_id: $auth.user.sub,
avatar: $auth.user.picture,
locale: $auth.user.locale
}
const { data } = await $axios.post(url, postData)
$auth.setToken('client', data.access_token)
$auth.setUser(data.client)
} catch (e) {
console.log(e)
}
}
}
The console.log($auth) error:
'$state': {
user: null,
loggedIn: false,
strategy: 'facebook'
},
error: TypeError: Cannot set property 'Authorization' of undefined
at Function.setHeader (server.js:1556:42)
at Oauth2Scheme._setToken (server.js:1014:31)
at Oauth2Scheme.mounted (server.js:1001:12)
at Auth.mounted (server.js:516:42)
at Auth.init (server.js:459:18)
at module.exports../.nuxt/auth/plugin.js._webpack_exports_.default (server.js:939:16)
at createApp (server.js:2432:87)
}
And my auth module config:
import dotenv from 'dotenv'
dotenv.config()
export const auth = {
plugins: [
// {
// src: '~/plugins/axios',
// ssr: true
// },
{
src: '~/plugins/auth-axios',
ssr: true
},
'~/plugins/auth.js'
],
redirect: {
login: '/',
callback: '/callback'
},
strategies: {
local: false,
facebook: {
client_id: '#############',
userinfo_endpoint:
'https://graph.facebook.com/v2.12/me?fields=about,name,picture{url},email,birthday',
scope: ['public_profile', 'email', 'user_birthday']
},
google: {
client_id:
'#####################'
}
}
}
Seems that auth when login is trying to set the Axios token (also when I log out it tries to remove it) but it fails. If I go to the Chrome dev tools and debug it to see which Axios instance is trying to use for that. Every time is the main Axios instance and it's supposed to be accessible there.
Screenshot from DevTools:
Screenshot from DevTools
Does someone know what I'm missing? Can I prevent auth facebook strategy to update any Axios instance? Or, can I specify which Axios instance to update (set / remove token)?
EDIT: Forcing the $auth.fetchUser my auth plugin code does work but with the error mentioned before. When I try to logOut it doesn't work due to the same error when trying to remove the token automatically)
I'm going crazy with this issue for two weeks now.
Thanks so much!

How do i fix an internal server error when authenticating with Next-Auth

I am trying to add authentication to my Next.js project with Next-Auth, however I am stuck on a 500 internal server error after submitting credentials (http://localhost:3000/api/auth/error?error=Configuration).
I think it might be something to do with running on http://localhost:3000 but I'm not sure. Can anyone see what i'm doing wrong?
pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
const options = {
site: 'http://localhost:3000',
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
consol.log('credentials', credentials);
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' };
if (user) {
return Promise.resolve(user);
} else {
return Promise.resolve(null);
}
},
}),
],
database: process.env.MONGO_URI,
};
export default (req, res) => NextAuth(req, res, options);
pages/index.js
import { useSession } from 'next-auth/client';
export default function Home() {
const [session, loading] = useSession();
console.log('session', session);
return (
<div className="container">
<main>
{session && <p>Signed in as {session.user.email}</p>}
{!session && (
<p>
Sign in
</p>
)}
</main>
</div>
);
}
pages/_app.js
import { Provider } from 'next-auth/client';
import '../styles.css';
export default ({ Component, pageProps }) => {
const { session } = pageProps;
return (
<Provider options={{ site: process.env.SITE }} session={session}>
<Component {...pageProps} />
</Provider>
);
};
next.config.js
module.exports = {
env: {
MONGO_URI: '...',
SITE: 'http://localhost:3000',
},
};
Any help would be really appreciated.
In your pages/api/auth/[...nextauth].js add:
secret:process.env.SECRET
SECRET must be any string value like this:
SECRET:LlKq6ZtYbr+hTC073mAmAh9/h2HwMfsFo4hrfCx6gts=
Also add it in your vercel env.
Thats it, your problem will be solved.
As mentioned by #Dinesh, my problem was solved by adding a NEXTAUTH_SECRET to both Vercel Environment Variables and to [...nextauth].ts
Local deployment worked without that variable, but vercel deployment needed the secret. In addition to adding the Vercel environment variable, my working [...nextauth].ts looks like:
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
async jwt({ token }) {
token.userRole = "user"
return token
},
},
})
You had a typo under your asyc function consol.log('credentials', credentials);
Fix to console.log('credentials', credentials); and that should solve the problem.
I faced a similar problem and it was because I didn't add any providers array.
In the future, fix any typos or errors within your [...nextauth].js to fix 500 error
Keep in mind NEXTAUTH_URL to be set for yourwebsite.com
process.env.NEXTAUTH_URL=https://yourwebsite.com

How to make Nuxt-auth and Nuxt-i18n to be friends

I want to use nuxt#auth module and nuxt-i18n together. The problem appears when I want to have different routes for login page. For example:
pages: {
login: {
en: '/authorization',
fr: '/autorisation'
}
}
Routes are working well, but when I try to use nuxt#auth with all page restricted, the default redirect asks for /login page. In nuxt.config.js file I can rewrite redirect route, but I can set only one redirect.
auth: {
redirect: {
login: '/fr/autorisation'
}
}
How can I show to auth module to ask for route of selected language? Thanks in advance!
Hope it'll be helpful for somebody =)
export default ({ app, $auth }) => {
$auth.onRedirect((to, from) => {
return app.localePath(to)
})
}
It seems that there was a solution for that problem https://github.com/nuxt-community/auth-module/pull/185, but I can't access to onRedirect method in the current release.
I did a workaround. I added auth-lang-redirect.js plugin, which overrides redirect option defined in the nuxt.config.js file.
export default ({ app }) => {
var redirect = app.$auth.$storage.options.redirect
for (var key in redirect) {
redirect[key] = '/' + app.i18n.locale + redirect[key]
}
app.$auth.$storage.options.redirect = redirect
}
Notice that I don't use nuxt-i18n module, but you should get the point.
You have to register this plugin in nuxt.config.js like this:
auth: {
strategies: { ... },
redirect: {
login: '/login',
logout: '/',
callback: '/login',
home: '/user/profile'
},
plugins: ['#/plugins/auth-lang-redirect.js']
},
export default function({ app, $auth }) {
const redirect = { ...$auth.$storage.options.redirect };
const localizeRedirects = () => {
for (const key in redirect) {
$auth.$storage.options.redirect[key] = app.localePath(redirect[key]);
}
};
localizeRedirects();
app.i18n.onLanguageSwitched = () => {
localizeRedirects();
};
}

Categories