DEV Community

Vikas Kamboj
Vikas Kamboj

Posted on

Building a Versioned REST API with Authentication in Yii2 Advanced

Yii2 Advanced comes with separate applications for frontend, backend, console, and shared code in common. It does not include an api application by default, but Yii2 Advanced is designed in a way that makes adding one straightforward.

In this post, we’ll create a separate API application, configure REST routes, add versioned URLs like /v1/users and /v2/users, and prepare the structure for authentication.

Yii2 Advanced Project Structure

A default Yii2 Advanced project usually looks like this:

backend/
common/
console/
frontend/
vendor/
composer.json
Enter fullscreen mode Exit fullscreen mode

To build an API, create a new application folder at the same level as frontend and backend:

api/
Enter fullscreen mode Exit fullscreen mode

After adding it, your structure becomes:

api/
backend/
common/
console/
frontend/
vendor/
composer.json
Enter fullscreen mode Exit fullscreen mode

Creating the API Application

Inside the api folder, create this structure:

api/
  config/
    main.php
    main-local.php
    params.php
  controllers/
  modules/
  runtime/
  web/
    index.php
Enter fullscreen mode Exit fullscreen mode

The api/web folder is the public document root for the API application.

Creating api/web/index.php

Create the entry file:

<?php

defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require __DIR__ . '/../../vendor/autoload.php';
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
require __DIR__ . '/../../common/config/bootstrap.php';

$config = yii\helpers\ArrayHelper::merge(
    require __DIR__ . '/../../common/config/main.php',
    require __DIR__ . '/../../common/config/main-local.php',
    require __DIR__ . '/../config/main.php',
    require __DIR__ . '/../config/main-local.php'
);

(new yii\web\Application($config))->run();
Enter fullscreen mode Exit fullscreen mode

This bootstraps Yii and loads the shared common config plus API-specific configuration.

Creating api/config/main.php

Create the main API config file:

<?php

return [
    'id' => 'app-api',
    'basePath' => dirname(__DIR__),
    'controllerNamespace' => 'api\controllers',

    'components' => [
        'request' => [
            'csrfParam' => '_csrf-api',
            'parsers' => [
                'application/json' => yii\web\JsonParser::class,
            ],
        ],

        'response' => [
            'format' => yii\web\Response::FORMAT_JSON,
        ],

        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
                'GET /' => 'site/index',
            ],
        ],
    ],
];
Enter fullscreen mode Exit fullscreen mode

The important parts are:

'parsers' => [
    'application/json' => yii\web\JsonParser::class,
],
Enter fullscreen mode Exit fullscreen mode

This allows Yii2 to read JSON request bodies.

And:

'response' => [
    'format' => yii\web\Response::FORMAT_JSON,
],
Enter fullscreen mode Exit fullscreen mode

This makes API responses return JSON by default.

Creating api/config/main-local.php

Create a local config file:

<?php

return [
    'components' => [
        'request' => [
            'cookieValidationKey' => 'generate-a-random-secret-key-here',
        ],
    ],
];
Enter fullscreen mode Exit fullscreen mode

Use a real random string for cookieValidationKey.

Creating api/config/params.php

Create an empty params file:

<?php

return [];
Enter fullscreen mode Exit fullscreen mode

Adding Autoloading

If your project does not already autoload the api namespace, add it to composer.json:

"autoload": {
  "psr-4": {
    "api\\": "api/"
  }
}
Enter fullscreen mode Exit fullscreen mode

Then run:

composer dump-autoload
Enter fullscreen mode Exit fullscreen mode

Creating a Test Controller

Create a simple controller to confirm the API app works.

<?php

namespace api\controllers;

use yii\rest\Controller;

class SiteController extends Controller
{
    public function actionIndex()
    {
        return [
            'status' => 'ok',
            'app' => 'api',
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

With this URL rule:

'rules' => [
    'GET /' => 'site/index',
],
Enter fullscreen mode Exit fullscreen mode

You should be able to open:

https://api.yourdomain.com/
Enter fullscreen mode Exit fullscreen mode

or locally:

http://localhost:8080/
Enter fullscreen mode Exit fullscreen mode

and see:

{
  "status": "ok",
  "app": "api"
}
Enter fullscreen mode Exit fullscreen mode

Where Do URL Rules Go?

URL rules are added inside the urlManager component in api/config/main.php:

'components' => [
    'urlManager' => [
        'enablePrettyUrl' => true,
        'showScriptName' => false,
        'rules' => [
            'GET /' => 'site/index',
        ],
    ],
],
Enter fullscreen mode Exit fullscreen mode

The rules array belongs inside:

components.urlManager
Enter fullscreen mode Exit fullscreen mode

not at the top level of the config.

Do You Need to Add Every API Endpoint Manually?

No. Yii2 REST URL rules can generate standard REST endpoints automatically.

For example:

[
    'class' => yii\rest\UrlRule::class,
    'controller' => 'user',
]
Enter fullscreen mode Exit fullscreen mode

generates standard REST routes like:

GET    /users
GET    /users/123
POST   /users
PUT    /users/123
PATCH  /users/123
DELETE /users/123
Enter fullscreen mode Exit fullscreen mode

Yii2 pluralizes REST controller names by default:

user -> /users
product -> /products
order -> /orders
Enter fullscreen mode Exit fullscreen mode

For multiple controllers, you can group them:

[
    'class' => yii\rest\UrlRule::class,
    'controller' => [
        'user',
        'product',
        'order',
        'payment',
        'notification',
    ],
],
Enter fullscreen mode Exit fullscreen mode

This gives each controller its standard REST URLs.

Adding Custom API Actions

For non-standard endpoints, use extraPatterns.

Example:

[
    'class' => yii\rest\UrlRule::class,
    'controller' => 'auth',
    'extraPatterns' => [
        'POST login' => 'login',
        'POST logout' => 'logout',
        'POST refresh-token' => 'refresh-token',
    ],
],
Enter fullscreen mode Exit fullscreen mode

This creates:

POST /auth/login
POST /auth/logout
POST /auth/refresh-token
Enter fullscreen mode Exit fullscreen mode

So even with many endpoints, you usually do not list all of them manually. You define REST controllers and add only custom routes where needed.

Complete URLs

The complete URL depends on your API base URL.

If your API is hosted at:

https://api.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

then the routes become:

GET    https://api.yourdomain.com/users
GET    https://api.yourdomain.com/users/123
POST   https://api.yourdomain.com/users
PUT    https://api.yourdomain.com/users/123
PATCH  https://api.yourdomain.com/users/123
DELETE https://api.yourdomain.com/users/123
Enter fullscreen mode Exit fullscreen mode

For custom auth routes:

POST https://api.yourdomain.com/auth/login
POST https://api.yourdomain.com/auth/logout
POST https://api.yourdomain.com/auth/refresh-token
Enter fullscreen mode Exit fullscreen mode

If running locally:

http://localhost:8080/users
http://localhost:8080/auth/login
Enter fullscreen mode Exit fullscreen mode

If pretty URLs are not configured correctly, URLs may include index.php:

http://localhost:8080/index.php/users
http://localhost:8080/index.php/auth/login
Enter fullscreen mode Exit fullscreen mode

Adding API Versioning

For versioned API URLs like:

https://api.yourdomain.com/v1/users
https://api.yourdomain.com/v2/users
Enter fullscreen mode Exit fullscreen mode

the recommended Yii2 approach is to use modules.

Create this structure:

api/
  modules/
    v1/
      Module.php
      controllers/
        UserController.php
    v2/
      Module.php
      controllers/
        UserController.php
Enter fullscreen mode Exit fullscreen mode

Creating the V1 Module

Create api/modules/v1/Module.php:

<?php

namespace api\modules\v1;

class Module extends \yii\base\Module
{
    public $controllerNamespace = 'api\modules\v1\controllers';
}
Enter fullscreen mode Exit fullscreen mode

Creating the V2 Module

Create api/modules/v2/Module.php:

<?php

namespace api\modules\v2;

class Module extends \yii\base\Module
{
    public $controllerNamespace = 'api\modules\v2\controllers';
}
Enter fullscreen mode Exit fullscreen mode

Registering API Modules

Register the modules in api/config/main.php:

'modules' => [
    'v1' => [
        'class' => api\modules\v1\Module::class,
    ],
    'v2' => [
        'class' => api\modules\v2\Module::class,
    ],
],
Enter fullscreen mode Exit fullscreen mode

Your config now includes the versioned modules.

Adding Versioned URL Rules

Inside components.urlManager.rules, add versioned REST rules:

'rules' => [
    [
        'class' => yii\rest\UrlRule::class,
        'controller' => [
            'v1/user',
            'v1/product',
            'v1/order',
        ],
    ],
    [
        'class' => yii\rest\UrlRule::class,
        'controller' => [
            'v2/user',
            'v2/product',
            'v2/order',
        ],
    ],
],
Enter fullscreen mode Exit fullscreen mode

Now Yii2 maps these controllers:

api/modules/v1/controllers/UserController.php
api/modules/v2/controllers/UserController.php
Enter fullscreen mode Exit fullscreen mode

to these URLs:

/v1/users
/v2/users
Enter fullscreen mode Exit fullscreen mode

Creating a Versioned Controller

Create api/modules/v1/controllers/UserController.php:

<?php

namespace api\modules\v1\controllers;

use yii\rest\ActiveController;

class UserController extends ActiveController
{
    public $modelClass = 'common\models\User';
}
Enter fullscreen mode Exit fullscreen mode

Now the following endpoints are available:

GET    /v1/users
GET    /v1/users/123
POST   /v1/users
PUT    /v1/users/123
PATCH  /v1/users/123
DELETE /v1/users/123
Enter fullscreen mode Exit fullscreen mode

For v2, create:

<?php

namespace api\modules\v2\controllers;

use yii\rest\ActiveController;

class UserController extends ActiveController
{
    public $modelClass = 'common\models\User';
}
Enter fullscreen mode Exit fullscreen mode

Now v2 endpoints are available:

GET    /v2/users
GET    /v2/users/123
POST   /v2/users
PUT    /v2/users/123
PATCH  /v2/users/123
DELETE /v2/users/123
Enter fullscreen mode Exit fullscreen mode

Adding Versioned Auth Endpoints

For an auth controller inside v1:

[
    'class' => yii\rest\UrlRule::class,
    'controller' => 'v1/auth',
    'extraPatterns' => [
        'POST login' => 'login',
        'POST logout' => 'logout',
        'POST refresh-token' => 'refresh-token',
    ],
],
Enter fullscreen mode Exit fullscreen mode

This creates:

POST /v1/auth/login
POST /v1/auth/logout
POST /v1/auth/refresh-token
Enter fullscreen mode Exit fullscreen mode

Your controller would live at:

api/modules/v1/controllers/AuthController.php
Enter fullscreen mode Exit fullscreen mode

Adding Bearer Token Authentication

Yii2 supports bearer token authentication with yii\filters\auth\HttpBearerAuth.

In a REST controller, add:

use yii\filters\auth\HttpBearerAuth;

public function behaviors()
{
    $behaviors = parent::behaviors();

    $behaviors['authenticator'] = [
        'class' => HttpBearerAuth::class,
    ];

    return $behaviors;
}
Enter fullscreen mode Exit fullscreen mode

Example:

<?php

namespace api\modules\v1\controllers;

use yii\rest\ActiveController;
use yii\filters\auth\HttpBearerAuth;

class UserController extends ActiveController
{
    public $modelClass = 'common\models\User';

    public function behaviors()
    {
        $behaviors = parent::behaviors();

        $behaviors['authenticator'] = [
            'class' => HttpBearerAuth::class,
        ];

        return $behaviors;
    }
}
Enter fullscreen mode Exit fullscreen mode

Requests must include an Authorization header:

Authorization: Bearer your-token-here
Enter fullscreen mode Exit fullscreen mode

Finding Users by Access Token

Your common\models\User model should implement:

public static function findIdentityByAccessToken($token, $type = null)
{
    return static::findOne(['access_token' => $token]);
}
Enter fullscreen mode Exit fullscreen mode

For this to work, your user table needs an access_token column, or you need a separate token table.

For production APIs, avoid storing permanent plain tokens. A better approach is usually:

short-lived access tokens
refresh tokens
hashed token storage
JWT
OAuth2
Enter fullscreen mode Exit fullscreen mode

The simple access_token example is useful for learning, but production authentication should be designed more carefully.

Recommended Final Structure

A versioned API app may look like this:

api/
  config/
    main.php
    main-local.php
    params.php
  modules/
    v1/
      Module.php
      controllers/
        AuthController.php
        UserController.php
        ProductController.php
    v2/
      Module.php
      controllers/
        AuthController.php
        UserController.php
        ProductController.php
  runtime/
  web/
    index.php
backend/
common/
console/
frontend/
Enter fullscreen mode Exit fullscreen mode

Final Notes

In Yii2 Advanced, an API is simply another application beside frontend and backend. You create the api folder manually, give it its own config and web entry point, then use REST URL rules and modules to keep the code organized.

The most common pattern is:

api/modules/v1/controllers/UserController.php -> /v1/users
api/modules/v2/controllers/UserController.php -> /v2/users
Enter fullscreen mode Exit fullscreen mode

This keeps your API versions cleanly separated in both the URL and the codebase.

Top comments (0)