Skip to content

Authorization

Authorization involves defining roles and permissions to enhance security and control access to specific features.

INFO

Make sure to add /api prefix in the API endpoint.

Preview of Authorization Implementation

In this case, I'll take an example from a user list page that can only be accessed by certain roles. Let's consider two roles: Super Admin and Shift Manager.

  • Super Admin, with the permission, can access this page.
    Super Admin view

    image

  • Shift Manager, without the permission, cannot access this page, even when attempting to fetch user data using Postman.
    Shift Manager view
    • Frontend preview: image

    • Postman response: image

Get Abilities Endpoint

GET /abilities

Headers

  • Content-Type: application/json
  • Authorization: Bearer {token}

Responses

200 OK
json
[
    "reports.create",
    "reports.update",
    "reports.view",
    "users.index",
    "users.create",
    "users.update",
    // and so on...
]
[
    "reports.create",
    "reports.update",
    "reports.view",
    "users.index",
    "users.create",
    "users.update",
    // and so on...
]
401 Unauthorized

Possibilities:

  • User is not active
json
{
	"message": "Unauthenticated."
}
{
	"message": "Unauthenticated."
}

Authorization BE Implementation

  • Create 4 tables: roles and permissions with a name column, as well as permission_role and role_user pivot tables.
  • Ensure app.php file contains the providers key, which references the AuthServiceProvider class.
    app.php
    php
    'providers' => [
        // Other Service Providers...
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
    ],
    'providers' => [
        // Other Service Providers...
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
    ],
  • Define gates in the AuthServiceProvider class and create a registerUserAccessToGates function. This function starts by retrieving available permissions and then checks whether the user has those permissions or not.
    AuthServiceProvider.php
    php
    class AuthServiceProvider extends ServiceProvider
    {
        public function boot()
        {
            $this->registerPolicies();
            $this->registerUserAccessToGates();
        }
    
        protected function registerUserAccessToGates()
        {
            try {
                foreach (Permission::pluck('name') as $permission) {
                    Gate::define($permission, function ($user) use ($permission) {
                        return $user->roles()->whereHas('permissions', function ($q) use ($permission) {
                            $q->where('name', $permission);
                        })->count() > 0;
                    });
                }
            } catch (\Exception $e) {
                info('registerUserAccessToGates: Database not found or not yet migrated. Ignoring user permissions while booting app.');
            }
        }
    }
    class AuthServiceProvider extends ServiceProvider
    {
        public function boot()
        {
            $this->registerPolicies();
            $this->registerUserAccessToGates();
        }
    
        protected function registerUserAccessToGates()
        {
            try {
                foreach (Permission::pluck('name') as $permission) {
                    Gate::define($permission, function ($user) use ($permission) {
                        return $user->roles()->whereHas('permissions', function ($q) use ($permission) {
                            $q->where('name', $permission);
                        })->count() > 0;
                    });
                }
            } catch (\Exception $e) {
                info('registerUserAccessToGates: Database not found or not yet migrated. Ignoring user permissions while booting app.');
            }
        }
    }
  • In UserController, within the method responsible for fetching the list of users (index), use the authorize helper provided by Laravel with the permission name as a parameter. This secures the endpoint according to permissions.
    UserController.php
    php
    class UserController extends Controller
    {
        public function index()
        {
            $this->authorize('users.index');
            $users = $this->userService->indexUser();
            if ($users->isNotEmpty()) {
                return UserIndexResource::collection($users);
            } else {
                return response(['message' => 'Akun tidak ditemukan', 'data' => []], 404);
            }
        }
    }
    class UserController extends Controller
    {
        public function index()
        {
            $this->authorize('users.index');
            $users = $this->userService->indexUser();
            if ($users->isNotEmpty()) {
                return UserIndexResource::collection($users);
            } else {
                return response(['message' => 'Akun tidak ditemukan', 'data' => []], 404);
            }
        }
    }

Authorization FE Implementation

  • @casl/vue and @casl/ability packages must be installed
  • Get abilities endpoint must be implemented
  • Utilize the can function and rules from AbilityBuilder with Ability as a parameter. Insert permissions into the can() function and update the ability.
    auth.js
    js
    import { ref, reactive, inject } from 'vue'
    import { AbilityBuilder, Ability } from '@casl/ability';
    import { ABILITY_TOKEN } from '@casl/vue';
    
    export default function useAuth() {
    	
        const ability = inject(ABILITY_TOKEN)
    
            // .. other variables and functions
            
        const getAbilities = async () => {
            axios.get('/api/abilities')
            .then(response => {
                const permissions = response.data
                const { can, rules } = new AbilityBuilder(Ability)
    
                can(permissions)
    
                ability.update(rules)
            })
            .catch(error => {
                router.push({ name: 'login' })
            })
        }
    }
    import { ref, reactive, inject } from 'vue'
    import { AbilityBuilder, Ability } from '@casl/ability';
    import { ABILITY_TOKEN } from '@casl/vue';
    
    export default function useAuth() {
    	
        const ability = inject(ABILITY_TOKEN)
    
            // .. other variables and functions
            
        const getAbilities = async () => {
            axios.get('/api/abilities')
            .then(response => {
                const permissions = response.data
                const { can, rules } = new AbilityBuilder(Ability)
    
                can(permissions)
    
                ability.update(rules)
            })
            .catch(error => {
                router.push({ name: 'login' })
            })
        }
    }
  • Call the getAbilities function within the authentication or user login process.
    auth.js
    js
    const loginUser = async (response) => {
        user.name = response.data.data.name
        user.role = response.data.data.role
        user.role_id = response.data.data.role_id
        user.env.bussinessName = response.data.data.env.bussinessName
        user.env.bussinessAddress = response.data.data.env.bussinessAddress
    
        if (isInArray(user.role_id, allowedRoles) === false) {
            logout()
        }
        if (user.name === '') {
            localStorage.setItem('loggedIn', JSON.stringify(false))
        } else {
            localStorage.setItem('loggedIn', JSON.stringify(true))
            await getAbilities() // <--- call getAbilities() here
            await router.push({ name: 'reports.index' })
        }
    }
    
    const getUser = () => {
        axios.get('/api/user')
        .then(response => {
            user.name = response.data.name
            let shortUsername = user.name.substring(0, 16)
            user.name = `${shortUsername} ...`
            user.role = response.data.role
            user.role_id = response.data.role_id
            user.env = response.data.env
    
            store.commit('setAuthUser', user)
    
            if (isInArray(user.role_id, allowedRoles) === false) {
                logout()
            }
            getAbilities() // <--- call getAbilities() here
            isLoadingUser.value = false;
        })
        .catch(error => {
            router.push({ name: 'login' })
        })
    }
    const loginUser = async (response) => {
        user.name = response.data.data.name
        user.role = response.data.data.role
        user.role_id = response.data.data.role_id
        user.env.bussinessName = response.data.data.env.bussinessName
        user.env.bussinessAddress = response.data.data.env.bussinessAddress
    
        if (isInArray(user.role_id, allowedRoles) === false) {
            logout()
        }
        if (user.name === '') {
            localStorage.setItem('loggedIn', JSON.stringify(false))
        } else {
            localStorage.setItem('loggedIn', JSON.stringify(true))
            await getAbilities() // <--- call getAbilities() here
            await router.push({ name: 'reports.index' })
        }
    }
    
    const getUser = () => {
        axios.get('/api/user')
        .then(response => {
            user.name = response.data.name
            let shortUsername = user.name.substring(0, 16)
            user.name = `${shortUsername} ...`
            user.role = response.data.role
            user.role_id = response.data.role_id
            user.env = response.data.env
    
            store.commit('setAuthUser', user)
    
            if (isInArray(user.role_id, allowedRoles) === false) {
                logout()
            }
            getAbilities() // <--- call getAbilities() here
            isLoadingUser.value = false;
        })
        .catch(error => {
            router.push({ name: 'login' })
        })
    }
  • On the user list page, in the script section, import the can function, and use it with the permission as a parameter within a v-if condition
    Users/Index.vue
    vue
    <template>
        <div v-if="can('users.index')">
            <!-- user list -->
        </div>
        <NotFoundComponent v-else />
    </template>
    
    <script setup>
        import { useAbility } from '@casl/vue'
        const { can } = useAbility()
    
        // other variables and functions
    </script>
    <template>
        <div v-if="can('users.index')">
            <!-- user list -->
        </div>
        <NotFoundComponent v-else />
    </template>
    
    <script setup>
        import { useAbility } from '@casl/vue'
        const { can } = useAbility()
    
        // other variables and functions
    </script>
  • This can be applied to other components or pages that require authorization.

Released under the MIT License.