wappcode/gql-pdss-auth

Maintainers

Package info

github.com/wappcode/gql-pdss-auth

pkg:composer/wappcode/gql-pdss-auth

Statistics

Installs: 185

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

4.0.1 2026-03-12 22:48 UTC

README

Librería completa de autenticación y autorización para aplicaciones con WAppCore (GQLPDSS). Provee servicios de autenticación mediante sesión PHP y JWT con un sistema robusto de roles y permisos.

📋 Tabla de Contenidos

🚀 Instalación

1. Instalación via Composer

composer require wappcode/gql-pdss-auth

2. Configuración de Entidades Doctrine

Agregue las entidades del paquete a la configuración de Doctrine en config/local.config.php:

<?php
return [
    'database' => [
        'connection' => [
            'driver' => 'pdo_mysql',
            'user' => 'usuario',
            'password' => 'password',
            'host' => 'localhost',
            'port' => 3306,
            'dbname' => 'app',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci'
        ],
        "entity_paths" => [
            realpath(__DIR__ . "/../../dev/modules/AppModule/Entities"),
            realpath(__DIR__ . "/../../vendor/wappcode/gql-pdss-auth/GPDAuth/src/Entities"),
        ]
    ]
];

3. Crear Esquema de Base de Datos

Opción A: CLI de Doctrine

vendor/bin/doctrine orm:schema-tool:create

Opción B: Script SQL Ejecute el archivo SQL incluido: sql/gpd_auth.sql

⚙️ Configuración Básica

Integración con GQLPDSS

Configure el módulo en su aplicación (normalmente en dev/public/index.php):

<?php
use GPDAuth\GPDAuthModule;

// Configuración básica (recomendada para GraphQL)
$app->addModules([
    new GPDAuthModule(
        exitUnauthenticated: false,  // Para GraphQL, permite validación granular por resolver
        publicRoutes: ['/login', '/register']  // Rutas que no requieren autenticación
    ),
    // Otros módulos...
    AppModule::class,
]);

Configuración de Módulos

<?php
// Para aplicaciones REST API tradicionales
new GPDAuthModule(
    exitUnauthenticated: true,   // Responde 401 si no está autenticado
    publicRoutes: ['/login', '/register', '/forgot-password']
);

// Para aplicaciones GraphQL (recomendado)
new GPDAuthModule(
    exitUnauthenticated: false,  // Permite validación a nivel de resolver
    publicRoutes: ['/login']
);

🏃‍♂️ Uso Rápido

Autenticación Básica

<?php
use GPDAuth\Contracts\AuthServiceInterface;

// En un resolver o controlador
$authService = $context->getServiceManager()->get(AuthServiceInterface::class);

// Login
try {
    $authService->login('username', 'password', 'local_user');
    echo "Login exitoso";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

// Verificar si está autenticado
$user = $authService->getAuthenticatedUser();
if ($user) {
    echo "Usuario: " . $user->getUsername();
}

// Logout
$authService->logout();

GraphQL Integration

<?php
// En un módulo GraphQL
use GPDAuth\Graphql\AuthResolverGuardFactory;
use GPDCore\Graphql\ResolverPipelineFactory;

class AppModule extends AbstractModule
{
    function getResolvers(): array
    {
        $echoResolve = fn($root, $args) => $args["message"];
        
        return [
            // Resolver público
            "Query::echo" => $echoResolve,
            
            // Resolver protegido que requiere autenticación
            'Query::echoProtected' => ResolverPipelineFactory::createPipeline($echoResolve, [
                AuthResolverGuardFactory::requireAuthenticated(),
            ]),
            
            // Resolver que requiere rol específico
            'Query::adminOnly' => ResolverPipelineFactory::createPipeline($someResolver, [
                AuthResolverGuardFactory::requireRole('admin'),
            ]),
        ];
    }
}

🔐 AuthService

El AuthServiceInterface es el corazón del sistema de autenticación:

Métodos Principales

<?php
use GPDAuth\Contracts\AuthServiceInterface;

$auth = $context->getServiceManager()->get(AuthServiceInterface::class);

// Autenticación
$auth->login(string $username, string $password, string $grantType): void;
$auth->logout(): void;
$user = $auth->getAuthenticatedUser(): ?AuthenticatedUserInterface;

Ejemplo Práctico en GraphQL

<?php
// Resolver de login
public static function createLoginResolve(): callable
{
    return function ($root, array $args, AppContextInterface $context, $info) {
        $username = $args["username"] ?? '';
        $password = $args["password"] ?? '';
        
        /** @var AuthServiceInterface */
        $auth = $context->getServiceManager()->get(AuthServiceInterface::class);
        
        try {
            $auth->login($username, $password, AuthenticatedUserType::LOCAL_USER->value);
            $user = $auth->getAuthenticatedUser();
            
            return [
                'success' => true,
                'user' => $user->toArray(),
                'message' => 'Login exitoso'
            ];
        } catch (Throwable $e) {
            throw new GQLException('Credenciales inválidas', 'INVALID_CREDENTIALS');
        }
    };
}

👥 Sistema de Roles y Permisos

Interfaz AuthenticatedUserInterface

<?php
// Verificación de roles
$user->hasRole(string $role): bool;
$user->hasAnyRole(array $roles): bool;
$user->hasAllRoles(array $roles): bool;

// Verificación de permisos
$user->hasPermission(string $resource, string $permission, ?string $scope = null): bool;
$user->hasAnyPermission(array $resources, array $permissions, ?array $scopes = null): bool;
$user->hasAllPermissions(array $resources, array $permissions, ?array $scopes = null): bool;

Ejemplos de Uso

<?php
$user = $auth->getAuthenticatedUser();

// Verificar roles
if ($user->hasRole('admin')) {
    echo "Usuario es administrador";
}

if ($user->hasAnyRole(['editor', 'publisher'])) {
    echo "Usuario puede editar contenido";
}

// Verificar permisos específicos
if ($user->hasPermission('posts', 'CREATE')) {
    echo "Usuario puede crear posts";
}

// Permisos con scope
if ($user->hasPermission('posts', 'EDIT', 'OWNER')) {
    echo "Usuario puede editar sus propios posts";
}

if ($user->hasPermission('posts', 'EDIT', 'ALL')) {
    echo "Usuario puede editar cualquier post";
}

// Permisos múltiples
if ($user->hasAllPermissions(['posts', 'comments'], ['CREATE', 'EDIT'], ['ALL'])) {
    echo "Usuario tiene control completo sobre posts y comentarios";
}

🛡️ Protección de Resolvers GraphQL

AuthResolverGuardFactory

Protege resolvers GraphQL con diferentes niveles de autorización:

<?php
use GPDAuth\Graphql\AuthResolverGuardFactory;

// Requiere autenticación (cualquier usuario logueado)
AuthResolverGuardFactory::requireAuthenticated();

// Requiere rol específico
AuthResolverGuardFactory::requireRole('admin');

// Requiere cualquiera de los roles
AuthResolverGuardFactory::requireAnyRole(['editor', 'publisher']);

// Requiere todos los roles
AuthResolverGuardFactory::requireAllRoles(['staff', 'verified']);

// Requiere permiso específico
AuthResolverGuardFactory::requirePermission('posts', 'CREATE');

// Requiere permiso con scope
AuthResolverGuardFactory::requirePermission('posts', 'EDIT', 'OWNER');

// Requiere cualquier permiso de la lista
AuthResolverGuardFactory::requireAnyPermission(
    ['posts', 'pages'], 
    ['CREATE', 'EDIT'], 
    ['ALL', 'OWNER']
);

// Requiere todos los permisos
AuthResolverGuardFactory::requireAllPermissions(
    ['posts'], 
    ['CREATE', 'EDIT', 'DELETE'], 
    ['ALL']
);

Ejemplo Completo de Protección

<?php
class AppModule extends AbstractModule
{
    function getResolvers(): array
    {
        return [
            // Público
            'Query::login' => FieldLogin::createResolve(),
            
            // Solo usuarios autenticados
            'Query::profile' => ResolverPipelineFactory::createPipeline(
                $profileResolver,
                [AuthResolverGuardFactory::requireAuthenticated()]
            ),
            
            // Solo administradores
            'Mutation::deleteUser' => ResolverPipelineFactory::createPipeline(
                $deleteUserResolver,
                [AuthResolverGuardFactory::requireRole('admin')]
            ),
            
            // Editores o publicadores
            'Mutation::publishPost' => ResolverPipelineFactory::createPipeline(
                $publishResolver,
                [AuthResolverGuardFactory::requireAnyRole(['editor', 'publisher'])]
            ),
            
            // Permiso específico con scope
            'Mutation::editPost' => ResolverPipelineFactory::createPipeline(
                $editPostResolver,
                [AuthResolverGuardFactory::requirePermission('posts', 'EDIT', 'OWNER')]
            ),
        ];
    }
}

🔒 Middleware de Autenticación

AuthMiddleware

El middleware valida automáticamente las solicitudes:

<?php
// Configuración en GPDAuthModule
new GPDAuthModule(
    exitUnauthenticated: true,   // true: responde 401 si no autenticado
                                // false: continúa y permite validación granular
    publicRoutes: ['/login', '/register', '/forgot-password']
);

Comportamiento del Middleware

  • exitUnauthenticated: true - Ideal para APIs REST

    • Responde inmediatamente con 401 si no está autenticado
    • Protege todas las rutas excepto las públicas
  • exitUnauthenticated: false - Ideal para GraphQL

    • Permite que las solicitudes continúen
    • AuthResolverGuardFactory maneja la validación por resolver

Acceso al Usuario en Request

<?php
// El middleware inyecta el usuario autenticado en el request
$request = $context->getContextAttribute(ServerRequestInterface::class);
$user = $request->getAttribute(AuthenticatedUserInterface::class);

if ($user instanceof AuthenticatedUserInterface) {
    echo "Usuario autenticado: " . $user->getUsername();
}

🎫 JWT Authentication

Módulo GPDAuthJWT

Para autenticación JWT, use el módulo adicional:

<?php
use GPDAuthJWT\GPDAuthJWTModule;

// Configurar junto con el módulo base
$app->addModules([
    new GPDAuthModule(exitUnauthenticated: false),
    new GPDAuthJWTModule(),
    AppModule::class,
]);

JWT vs Session

  • Session Auth: Usa sesiones PHP, ideal para aplicaciones web tradicionales
  • JWT Auth: Tokens sin estado, ideal para APIs y aplicaciones SPA/móviles

OAuth 2.0 Client Credentials

<?php
// Endpoint para obtener tokens JWT
// POST /oauth/token
$response = [
    'grant_type' => 'client_credentials',
    'client_id' => 'your_client_id',  
    'client_secret' => 'your_client_secret',
    'scope' => 'read write'
];

Configuración Apache para JWT

Para que la autenticación JWT funcione correctamente con Apache, es necesario configurar el servidor para que pase el header Authorization:

# En VirtualHost o .htaccess
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

# O usando RewriteRule
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

📚 API Reference

Interfaces Principales

AuthServiceInterface

interface AuthServiceInterface
{
    public function login(string $username, string $password, string $grantType);
    public function logout(): void;
    public function getAuthenticatedUser(): ?AuthenticatedUserInterface;
}

AuthenticatedUserInterface

interface AuthenticatedUserInterface
{
    // Información del usuario
    public function getId(): string;
    public function getUsername(): string;
    public function getFullName(): string;
    public function getEmail(): ?string;
    public function toArray(): array;
    
    // Roles
    public function hasRole(string $role): bool;
    public function hasAnyRole(array $roles): bool;
    public function hasAllRoles(array $roles): bool;
    
    // Permisos
    public function hasPermission(string $resource, string $permission, ?string $scope = null): bool;
    public function hasAnyPermission(array $resources, array $permission, ?array $scopes = null): bool;
    public function hasAllPermissions(array $resources, array $permission, ?array $scopes = null): bool;
}

GraphQL Schema

El módulo proporciona los siguientes tipos y queries GraphQL:

type Query {
    login(username: String!, password: String!): AuthenticatedUser
    getSessionData: AuthenticatedUser
}

type AuthenticatedUser {
    fullName: String!
    firstName: String!
    lastName: String
    username: String!
    email: String
    picture: String
    roles: [String!]!
    permissions: [String!]!
}

Enums y Tipos

<?php
// Tipos de usuario
enum AuthenticatedUserType: string
{
    case API_CLIENT = 'api_client';
    case LOCAL_USER = 'local_user';
    case EXTERN_USER = 'extern_user';
}

// Métodos de autenticación
enum AuthMethod: string
{
    case Session = 'SESSION';
    case Jwt = 'JWT';
    case JwtOrSession = 'JWT_OR_SESSION';
    case SessionOrJwt = 'SESSION_OR_JWT';
}

Entidades de Base de Datos

La librería incluye las siguientes entidades Doctrine:

  • User: usuarios del sistema
  • Role: roles asignables a usuarios
  • Resource: recursos protegidos
  • Permission: permisos específicos sobre recursos
  • ApiConsumer: clientes API para JWT
  • TrustedIssuer: emisores JWT confiables

💡 Ejemplos Adicionales

Personalización de Resolvers con Usuario

<?php
$proxyEcho = fn($resolver) => function ($root, $args, AppContextInterface $context, $info) use ($resolver) {
    /** @var AuthServiceInterface */
    $authService = $context->getServiceManager()->get(AuthServiceInterface::class);
    $user = $authService->getAuthenticatedUser();
    
    if (!$user) {
        return $resolver($root, $args, $context, $info);
    }
    
    $msg = $resolver($root, $args, $context, $info);
    return sprintf("%s -> Usuario: %s", $msg, $user->getUsername());
};

return [
    'Query::echoWithUser' => ResolverPipelineFactory::createPipeline($echoResolve, [
        ResolverPipelineFactory::createWrapper($proxyEcho),
        AuthResolverGuardFactory::requireAuthenticated(),
    ]),
];

Manejo de Errores

<?php
use GPDAuth\Library\NoSignedException;
use GPDAuth\Library\NoAuthorizedException;

try {
    $user = static::getAuthenticatedUser($context);
    if (!$user) {
        throw new NoSignedException();
    }
    
    if (!$user->hasRole('admin')) {
        throw new NoAuthorizedException("Acceso denegado", "FORBIDDEN", 403);
    }
    
    // Lógica del resolver...
    
} catch (NoSignedException $e) {
    throw new GQLException('Debe iniciar sesión', 'UNAUTHENTICATED', 401);
} catch (NoAuthorizedException $e) {
    throw new GQLException('Permisos insuficientes', 'FORBIDDEN', 403);
}

Uso sin GQLPDSS

Para usar la librería en proyectos sin GQLPDSS:

composer require wappcode/gql-pdss-auth

Agregue las entidades a la configuración de Doctrine:

$entityPaths = [
    __DIR__ . "/../vendor/wappcode/gql-pdss-auth/GPDAuth/src/Entities"
];

Cree una instancia del servicio:

<?php
use GPDAuth\Services\AuthSessionService;
use GPDAuth\Services\UserRepository;

$userRepository = new UserRepository($entityManager);
$authService = new AuthSessionService($userRepository);

// Uso del servicio
$authService->login('username', 'password', 'local_user');
$user = $authService->getAuthenticatedUser();

Este sistema de autenticación proporciona una base sólida y flexible para manejar la seguridad en aplicaciones PHP modernas con GraphQL y REST APIs.

🤝 Contribución

Para contribuir al proyecto:

  1. Fork el repositorio
  2. Crea una rama para tu feature (git checkout -b feature/amazing-feature)
  3. Commit tus cambios (git commit -m 'Add some amazing feature')
  4. Push a la rama (git push origin feature/amazing-feature)
  5. Abre un Pull Request

📄 Licencia

Este proyecto está licenciado bajo la Licencia MIT - ver el archivo LICENSE para más detalles.