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 when facades.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.