QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.
- Source
- Introdução
- Parte I - ASP.NET - Inicializando os Projetos
- Parte 2 - PostgreSQL
- Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente
- Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity
- Parte 5 - ASP.NET - Documentação Interativa com Swagger
- Parte 6 - ASP.NET - Regionalização
- Parte 7 - ASP.NET - Autenticação e Autorização
- Parte 8 - ASP.NET - CORS
- Parte 9 - Quasar - Criação e Configuração do Projeto
- Parte 10 - Quasar - Configurações e Customizações
- Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR
- Parte 12 - Quasar - Serviços
- Parte 13 - Quasar - Regionalização e Stores
- Parte 14 - Quasar - Consumindo a API
- Parte 15 - Quasar - Login
- Parte 16 - Quasar - Áreas Protegidas
- Parte 17 - Quasar - Registro
- Parte 18 - Docker - Maquina Virtual Linux
- Parte 19 - Docker - Registro e Build
- Parte 20 - Docker - Traefik e Publicação
- Demo Online
33 Área Principal/Protegida - Parte I.
Agora, precisamos criar um segundo layout, que será utilizado pelas paginas protegidas.
Iremos adicionar a fonte mdi-v5
, por ele ter um maior leques de ícones que a fonte oficial do google, isto será feito em quasar.config.js > extras > mdi-v5
.
module.exports = function (ctx) { return { extras: [ 'mdi-v5' ] } }
Então crie a pasta main
em QPANC.App/src/layouts
e adicione os arquivos store.js
, index.js
, index.sass
e index.vue
QPANC.App/src/layouts/main/store.js
import { factory } from '@toby.mosque/utils' class MainLayoutModel { constructor ({ leftDrawerOpen = false } = {}) { this.leftDrawerOpen = leftDrawerOpen } } const options = { model: MainLayoutModel } export default factory.store({ options, actions: { async initialize ({ state }, { route, next }) { }, async logout (context) { await this.$axios.delete('/Auth/Logout') commit('app/token', undefined, { root: true }) this.$router.push('/login') } } }) export { options, MainLayoutModel }
QPANC.App/src/layouts/main/index.js
import EssentialLink from 'components/EssentialLink' import { factory } from '@toby.mosque/utils' import store, { options } from './store' const moduleName = 'layout-main' export default factory.page({ name: 'MainLayout', options, moduleName, storeModule: store, components: { EssentialLink }, data () { return { essentialLinks: [ { title: 'Docs', caption: 'quasar.dev', icon: 'school', link: 'https://quasar.dev' }, { title: 'Github', caption: 'github.com/quasarframework', icon: 'code', link: 'https://github.com/quasarframework' }, { title: 'Discord Chat Channel', caption: 'chat.quasar.dev', icon: 'chat', link: 'https://chat.quasar.dev' }, { title: 'Forum', caption: 'forum.quasar.dev', icon: 'record_voice_over', link: 'https://forum.quasar.dev' }, { title: 'Twitter', caption: '@quasarframework', icon: 'rss_feed', link: 'https://twitter.quasar.dev' }, { title: 'Facebook', caption: '@QuasarFramework', icon: 'public', link: 'https://facebook.quasar.dev' } ] } }, methods: { logout () { return this.$store.dispatch('layout-main/logout') } } })
QPANC.App/src/layouts/main/index.sass
#layout-main
QPANC.App/src/layouts/main/index.html
<template> <q-layout id="layout-main" view="lHh Lpr lFf" class="bg-main"> <q-header elevated> <q-toolbar> <q-btn flat dense round icon="menu" aria-label="Menu" @click="leftDrawerOpen = !leftDrawerOpen" /> <q-toolbar-title> Quasar App </q-toolbar-title> <div> Quasar v{{ $q.version }} <q-btn flat icon="mdi-exit-to-app" label="logout" @click="logout"></q-btn> </div> </q-toolbar> </q-header> <q-drawer v-model="leftDrawerOpen" show-if-above bordered content-class="bg-content" > <q-list> <q-item-label header> Essential Links </q-item-label> <EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" /> </q-list> </q-drawer> <q-page-container> <router-view /> </q-page-container> </q-layout> </template> <script src="./index.js"></script> <style src="./index.sass" lang="sass"></style>
Note que, este é basicamente o layout padrão do quasar, adaptado para o modo dark
/light
e com a função de logout
.
E agora, vamos adicionar a nossa primeira pagina que irá utilizar este layout. Crie a pasta home
na pasta QPANC.App/src/pages
e adicione os arquivos store.js
, index.js
, index.sass
e index.vue
QPANC.App/src/pages/home/store.js
import { factory } from '@toby.mosque/utils' class HomePageModel { } const options = { model: HomePageModel } export default factory.store({ options, actions: { async initialize ({ state }, { route, next }) { } } }) export { options, HomePageModel }
QPANC.App/src/pages/home/index.js
import { factory } from '@toby.mosque/utils' import store, { options } from './store' const moduleName = 'page-home' export default factory.page({ name: 'HomePage', options, moduleName, storeModule: store })
QPANC.App/src/pages/home/index.sass
#page-home
QPANC.App/src/pages/home/index.vue
<template> <q-page id="page-home" class="flex flex-center"> Home </q-page> </template> <script src="./index.js"></script> <style src="./index.sass" lang="sass"></style>
tanto trabalho para exibir a palavra Home
, mas precisávamos de uma pagina apenas.
Agora, crie o arquivo main.js
na pasta QPANC.App/src/router/areas
e adicione as seguintes rotas.
QPANC.App/src/router/areas/main.js
export default function (context) { return { path: '', component: () => import('layouts/main/index.vue'), children: [ { name: 'home', path: 'home', component: () => import('pages/home/index.vue') } ], meta: { authorize: true } } }
Note a presença do meta
, nós usamos este campo, quando queremos adiciona alguma informação de controle a rota, neste caso, que o usuário precisa está logado para acessar este componente (e os seus respectivos filhos).
inclua a área main
no arquivo routes.js
:
QPANC.App/src/router/routes.js
import clean from './areas/clean' import main from './areas/main' export default function (context) { const routes = [{ path: '/', component: { /* ... */ }, children: [ { path: '', beforeEnter (to, from, next) { /* ... */ } }, clean(context), main(context) ] }] // Always leave this as last one if (process.env.MODE !== 'ssr') { /*...*/ } return routes }
E agora vamos criar um navigation guard
global, que fará uso do meta authorize
, para isto, precisamos fazer a seguinte alteração no index.js
.
QPANC.App/src/router/index.js
/* ... */ export default function (context) { context.router = new VueRouter({ /*...*/ }) context.router.beforeEach((to, from, next) => { const { store } = context let protectedRoutes = to.matched.filter(route => route.meta.authorize) if (protectedRoutes.length > 0) { const logged = store.getters['app/isLogged']() if (!logged) { next('/login') } } next() }) return context.router }
Então execute a aplicação e tente acessar a raiz da aplicação (rota '/'), e veja que seja redirecionado para '/login' se não estiver logado e '/home' se estiver.
Caso coloque um console.log({ path: to.path, authorize: requireAuth })
no beforeEach
, você verá que para o path '/' o protectedRoutes.length
será 0
.
Caso tente acessar a pagina de login (rota /login
) enquanto logado, você terá acesso a esta rota, o que é esperado.
Caso tente acessar a pagina home (rota /home
) enquanto não está logado, você será enviado para a rota /login
, o que também é esperado.
34 Área Principal/Protegida - Parte 2.
Agora, iremos cria uma segunda pagina, só que para acessar esta pagina, o usuário além de autenticado, precisa está autorizado, neste caso, a autorização é feita apenas para os usuários que tenham a role
Developer
O primeiro passo, é criar uma nova page, copie a pasta home
e renomeie a copia para devboard
, e claro, em prol da consistência, não esqueça de renomear qual quer incidência da palavra home
para devboard
.
Agora que temos um componente, temos que criar um rota para ele, isto será feito no arquivo main.js
em QPANC.App/src/router/areas
QPANC.App/src/router/areas/main.js
export default function (context) { return { path: '', component: () => import('layouts/main/index.vue'), children: [ { name: 'home', path: 'home', component: () => import('pages/home/index.vue') }, { name: 'devboard', path: 'devboard', component: () => import('pages/devboard/index.vue'), meta: { authorize: { roles: ['Developer'] } } } ], meta: { authorize: true } } }
Note que os meta
authorize
, tentam ter o mesmo comportamento que o AuthorizeAttribute
no C#, desta forma, todos os componentes com authorize
serão testando, o que na pratica combina todos os authorize
.
O segundo ponto, é que no campo roles
é possível declarar múltiplas role
, porém teremos um OR
e não um AND
. exemplo, se tivemos ['Developer', 'Admin']
, então basta que o usuário seja um Admin
ou Developer
para ter acesso a esta rota.
O próximo passo, é incluir alguns getters
na store app
, estes getters
serão responsável por testar de o usuário possui algumas das roles declaradas.
QPANC.App/src/store/app.js
import { factory } from '@toby.mosque/utils' /*...*/ const roleClaimName = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role' export default factory.store({ options, getters: { /*...*/ roles (state, getters) { if (!getters.decoded || !getters.decoded.exp) { return [] } const roles = getters.decoded[roleClaimName] if (Array.isArray(roles)) { return roles } else { return [roles] } }, isOnRoles (state, getters) { return (roles) => { return getters.roles.some(role => roles.include(role)) } } } })
Note que, ao decompor o token JWT
, o campo http://schemas.microsoft.com/ws/2008/06/identity/claims/role
poderá ser um array
ou uma string
, caso ele seja uma string, devemos retornar um array
tendo ele como único elemento.
O getter
isOnRoles
testa se o usuário logado possui pelo menos uma role que pertença a lista de roles
passada.
Agora, precisamos fazer uma alteração no beforeEach
que está no index.js
em QPANC.App/src/router
, para que ele faça uso dos novos getters
.
QPANC.App/src/router/index.js
router.beforeEach((to, from, next) => { const { store } = context let protectedRoutes = to.matched.filter(route => route.meta.authorize) if (protectedRoutes.length > 0) { const logged = store.getters['app/isLogged']() if (!logged) { return next('/login') } protectedRoutes = protectedRoutes.filter(route => route.meta.authorize.roles) if (protectedRoutes.length > 0) { for (const protectedRoute of protectedRoutes) { const { roles } = protectedRoute.meta.authorize const isOnRoles = store.getters['app/isOnRoles'](roles) if (!isOnRoles) { return next('/home') } } } } next() })
Perceba que, caso o usuário não esteja logado, e tenta acessar uma área que requer autenticação, ele será redirecionado para a tela de login
, porém se estiver logado, mas não tenha autorização, ele seja redirecionado para a tela inicial (home
).
E para testamos este fluxo, iremos criar um QBtn
na pagina home
, que deve ser visível apenas para usuários com a role Developer
QPANC.App/src/pages/home/index.vue
<template> <q-page id="page-home" class="flex flex-center"> Home <template v-if="isDeveloper"> <q-btn to="/devboard" :label="$('actions.devboard')" /> </template> </q-page> </template> <script src="./index.js"></script> <style src="./index.sass" lang="sass"></style>
QPANC.App/src/pages/home/index.js
import { factory } from '@toby.mosque/utils' import store, { options } from './store' const moduleName = 'page-home' export default factory.page({ name: 'HomePage', options, moduleName, storeModule: store, computed: { isOnRoles () { return this.$store.getters['app/isOnRoles'] }, isDeveloper () { return this.isOnRoles(['Developer']) } } })
Quasar.App/src/i18n/en-us/index.js
export default { actions: { devboard: 'Developer Board' } }
Quasar.App/src/i18n/pt-br/index.js
export default { actions: { devboard: 'Painel do Desenvolvedor' } }
Agora, acesse a pagina Home
, e desde que seja um Developer
, você verá um QBtn
que irá redirecionar para a pagina Devboard
.
Agora, tente alterar a regra de autorização, para ao invés de testar se o usuário é um Developer
, testar se é ele um Admin
(estou suponto que o seu usuário não é um Admin
).
Neste caso, o QBtn
da pagina Home
deverá desaparecer, e caso tente acessar a pagina Devboard
diretamente, será redirecionado para a Home
, como este redirecionamento é feito no servidor, a pagina Devboard
sequer será renderizada.
Top comments (0)