Authorization
Introduction
In addition to providing built-in authentication services, Goravel also provides a simple way to authorize user actions against a given resource. For example, even though a user is authenticated, they may not be authorized to update or delete certain Eloquent models or database records managed by your application. Goravel's authorization features provide an easy, organized way of managing these types of authorization checks.
Laravel provides two primary ways of authorizing actions: gates and policies. Think of gates and policies like routes and controllers. Gates provide a simple, closure-based approach to authorization while policies, like controllers, group logic around a particular model or resource. In this documentation, we'll explore gates first and then examine policies.
You do not need to choose between exclusively using gates or exclusively using policies when building an application. Most applications will most likely contain some mixture of gates and policies, and that is perfectly fine!
Gates
Writing Gates
Gates are simply closures that determine if a user is authorized to perform a given action. Typically, gates are defined within the Boot
method of the app/providers/auth_service_provider.go
file using the Gate facade.
In this example, we'll define a gate to determine if a user can update a given Post model by comparing the user's id against the user_id of the user that created the post:
package providers
import (
"context"
"github.com/goravel/framework/contracts/auth/access"
"github.com/goravel/framework/facades"
)
type AuthServiceProvider struct {
}
func (receiver *AuthServiceProvider) Register() {
}
func (receiver *AuthServiceProvider) Boot() {
facades.Gate.Define("update-post", func(ctx context.Context, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
if user.ID == post.UserID {
return access.NewAllowResponse()
} else {
return access.NewDenyResponse("error")
}
})
}
Authorizing Actions
To authorize an action using gates, you should use the Allows
or Denies
methods provided by the Gate facade:
package controllers
import (
"github.com/goravel/framework/facades"
)
type UserController struct {
func (r *UserController) Show(ctx http.Context) {
var post models.Post
if facades.Gate.Allows("update-post", map[string]any{
"post": post,
}) {
}
}
You may authorize multiple actions at a time using the Any
or None
methods:
if facades.Gate.Any([]string{"update-post", "delete-post"}, map[string]any{
"post": post,
}) {
// The user can update or delete the post...
}
if facades.Gate.None([]string{"update-post", "delete-post"}, map[string]any{
"post": post,
}) {
// The user can't update or delete the post...
}
Gate Responses
The Allows
method will return a simple boolean value, you can use the Inspect
method to get the full authorization response returned by the gate:
response := facades.Gate.Inspect("edit-settings", nil);
if (response.Allowed()) {
// The action is authorized...
} else {
fmt.Println($response->message());
}
Intercepting Gate Checks
Sometimes, you may wish to grant all abilities to a specific user. You may use the Before
method to define a closure that is run before all other authorization checks:
facades.Gate.Before(func(ctx context.Context, ability string, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
}
return nil
})
If the before closure returns a non-nil result that result will be considered the result of the authorization check.
You may use the After
method to define a closure to be executed after all other authorization checks:
facades.Gate.After(func(ctx context.Context, ability string, arguments map[string]any, result access.Response) access.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
}
return nil
})
Notice: The returned result of
After
will be applied only whenfacades.Gate.Define
returns nil.
Inject Context
The context
will be passed to the Before
, After
, Define
methods.
facades.Gate.WithContext(ctx).Allows("update-post", map[string]any{
"post": post,
})
Policies
Generating Policies
You may generate a policy using the make:policy
Artisan command. The generated policy will be placed in the app/policies
directory. If this directory does not exist in your application, Goravel will create it for you:
go run . artisan make:policy PostPolicy
Writing Policies
You can add methods for the policy, for example, let's define an Update
method on our PostPolicy
which determines if a models.User
can update a models.Post
.
package policies
import (
"context"
"github.com/goravel/framework/contracts/auth/access"
)
type PostPolicy struct {
}
func NewPostPolicy() *PostPolicy {
return &PostPolicy{}
}
func (r *PostPolicy) Update(ctx context.Context, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
if user.ID == post.UserID {
return access.NewAllowResponse()
} else {
return access.NewDenyResponse("You do not own this post.")
}
}
Then we can register the policy to app/providers/auth_service_provider.go
:
facades.Gate.Define("update-post", policies.NewPostPolicy().Update)
You may continue to define additional methods on the policy as needed for the various actions it authorizes. For example, you might define View
or Delete
methods to authorize various models.Post
related actions, but remember you are free to give your policy methods any name you like.