8000 Implement basic JWT RBAC API by acwilan · Pull Request #2 · homeputers/ebal · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement basic JWT RBAC API #2

8000 New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# ebal

This project is a simple Yii2 based REST API backend. It uses MySQL for
storage and JSON Web Tokens (JWT) for authentication. Example endpoints are
defined in `SiteController` and demonstrate role based access control.

## Database Migrations

Run the following command from the `backend` directory to apply migrations:

```bash
./yii migrate
```

This will create the required tables such as the `user` table.
8 changes: 8 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ Then you can start a PHP server from the `web` directory:
```
php -S localhost:8080
```

### Running Migrations

After installing dependencies, apply migrations with:

```bash
./yii migrate
```
21 changes: 21 additions & 0 deletions backend/components/Jwt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
namespace app\components;

use Firebase\JWT\JWT as BaseJwt;
use Firebase\JWT\Key;

class Jwt
{
public string $key;
public string $alg = 'HS256';

public function encode(array $payload): string
{
return BaseJwt::encode($payload, $this->key, $this->alg);
}

public function decode(string $token): array
{
return (array) BaseJwt::decode($token, new Key($this->key, $this->alg));
}
}
8 changes: 7 additions & 1 deletion backend/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"type": "project",
"require": {
"php": ">=8.0.0",
"yiisoft/yii2": "~2.0.47"
"yiisoft/yii2": "~2.0.47",
"firebase/php-jwt": "^6.9"
},
"require-dev": {
"yiisoft/yii2-debug": "~2.1.0",
Expand All @@ -13,5 +14,10 @@
"config": {
"process-timeout": 1800,
"sort-packages": true
},
"autoload": {
"psr-4": {
"app\\": ""
}
}
}
24 changes: 24 additions & 0 deletions backend/config/console.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
return [
'id' => 'backend-console',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'controllerNamespace' => 'app\commands',
'components' => [
'db' => require __DIR__ . '/db.php',
'authManager' => [
'class' => yii\rbac\DbManager::class,
],
'cache' => [
'class' => yii\caching\FileCache::class,
],
'log' => [
'targets' => [
[
'class' => yii\log\FileTarget::class,
'levels' => ['error', 'warning'],
],
],
],
],
];
8 changes: 8 additions & 0 deletions backend/config/db.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
return [
'class' => yii\db\Connection::class,
'dsn' => getenv('DB_DSN') ?: 'mysql:host=127.0.0.1;dbname=app',
'username' => getenv('DB_USER') ?: 'root',
'password' => getenv('DB_PASS') ?: '',
'charset' => 'utf8',
];
19 changes: 19 additions & 0 deletions backend/config/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,29 @@
'components' => [
'request' => [
'cookieValidationKey' => 'changeit',
'parsers' => [
'application/json' => yii\web\JsonParser::class,
],
],
'response' => [
'format' => yii\web\Response::FORMAT_JSON,
],
'cache' => [
'class' => yii\caching\FileCache::class,
],
'user' => [
'identityClass' => app\models\User::class,
'enableSession' => false,
'loginUrl' => null,
],
'jwt' => [
'class' => app\components\Jwt::class,
'key' => getenv('JWT_KEY') ?: 'secret',
],
'authManager' => [
'class' => yii\rbac\DbManager::class,
],
'db' => require __DIR__ . '/db.php',
'log' => [
'targets' => [
[
Expand Down
62 changes: 59 additions & 3 deletions backend/controllers/SiteController.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,68 @@
<?php
namespace app\controllers;

use yii\web\Controller;
use yii\rest\Controller;
use yii\filters\AccessControl;
use yii\filters\auth\HttpBearerAuth;
use Yii;

class SiteController extends Controller
{
public function actionIndex()
public function behaviors()
{
return $this->render('index');
$behaviors = parent::behaviors();

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

$behaviors['access'] = [
'class' => AccessControl::class,
'only' 9E12 => ['dashboard', 'admin'],
'rules' => [
[
'actions' => ['dashboard'],
'allow' => true,
'roles' => ['@'],
],
[
'actions' => ['admin'],
'allow' => true,
'roles' => ['@'],
'matchCallback' => function () {
return Yii::$app->user->identity->role === 'admin';
},
],
],
];

return $behaviors;
}

public function actionPublic()
{
return ['message' => 'Public endpoint'];
}

public function actionLogin()
{
$body = Yii::$app->request->bodyParams;
$user = \app\models\User::findOne(['username' => $body['username'] ?? null]);
if ($user && Yii::$app->security->validatePassword($body['password'] ?? '', $user->password_hash)) {
return ['token' => $user->generateJwt()];
}
Yii::$app->response->statusCode = 401;
return ['error' => 'Invalid credentials'];
}

public function actionDashboard()
{
return ['message' => 'Dashboard', 'user' => Yii::$app->user->identity->username];
}

public function actionAdmin()
{
return ['message' => 'Admin area'];
}
}
23 changes: 23 additions & 0 deletions backend/migrations/m230101_000000_create_user_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
use yii\db\Migration;

/**
* Handles the creation of table `{{%user}}`.
*/
class m230101_000000_create_user_table extends Migration
{
public function safeUp()
{
$this->createTable('{{%user}}', [
'id' => $this->primaryKey(),
'username' => $this->string()->notNull()->unique(),
'password_hash' => $this->string()->notNull(),
'role' => $this->string()->notNull()->defaultValue('user'),
]);
}

public function safeDown()
{
$this->dropTable('{{%user}}');
}
}
49 changes: 49 additions & 0 deletions backend/models/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
namespace app\models;

use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
use Yii;

class User extends ActiveRecord implements IdentityInterface
{
public static function tableName()
{
return '{{%user}}';
}

public static function findIdentity($id)
{
return static::findOne($id);
}

public static function findIdentityByAccessToken($token, $type = null)
{
$data = Yii::$app->jwt->decode($token);
return static::findOne($data['id'] ?? null);
}

public function getId()
{
return $this->id;
}

public function getAuthKey()
{
return null;
}

public function validateAuthKey($authKey)
{
return false;
}

public function generateJwt(): string
{
$payload = [
'id' => $this->id,
'role' => $this->role,
];
return Yii::$app->jwt->encode($payload);
}
}
10 changes: 10 additions & 0 deletions backend/yii
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env php
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';

$config = require __DIR__ . '/config/console.php';

$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);
0