A demonstration application showcasing secure authentication using access and refresh tokens, built with vanilla PHP, GraphQL, and a React frontend.
This project demonstrates a modern authentication flow using access and refresh tokens. The backend is implemented in vanilla PHP with GraphQL, while the frontend is a React app. The goal is to illustrate best practices for token-based authentication, including token expiry, refresh logic, and secure storage.
Backend:
- PHP (vanilla)
- webonyx/graphql-php (GraphQL server)
- doctrine/orm (ORM for database)
- firebase/php-jwt (JWT encoding/decoding handling)
- vlucas/phpdotenv (Environment variables)
- nikic/fast-route (Routing)
- symfony/cache (Caching)
Frontend:
- React
- @apollo/client (GraphQL client)
- react-router (Routing)
- tailwindcss (Styling)
- react-hot-toast (Notifications)
- react-hook-form (Form handling)
-
Access Token:
- Issued on login/register.
- Valid for 15 minutes.
- Stored in localStorage on the frontend.
(Note: Storing tokens in localStorage is not recommended for production due to XSS risks, but this is a demo and the app should not be vulnerable to XSS attacks.)
-
Refresh Token:
- Issued on login/register.
- Valid for 7 days.
- Stored in the database and sent to the client as an httpOnly cookie.
- Used to obtain new access tokens when the current one expires.
- (To avoid CSRF attacks, a CSRF token should be added, but this is just a demo.)
-
Token Refresh:
- When the access token expires, the frontend automatically requests a new access token using the refresh token cookie.
- If the refresh token is expired or invalid, the user is logged out and must log in again.
-
Upcoming Features:
- Refresh token rotation (functions for this are almost ready and will be added in future commits).
Auth Server and Resource Server are physically the same in this project.
sequenceDiagram
participant Client as Client<br/>(app or user)
participant AuthServer as Auth Server<br/>(/refresh endpoint)
participant RefreshStore as Refresh Token<br/>Store
participant ResourceServer as Resource Server
Note over Client: Access token expires
Client->>AuthServer: POST /gql<br/>mutation=refreshToken<br/>refresh_token=[token]
AuthServer->>RefreshStore: Verify refresh token
RefreshStore-->>AuthServer: Token validation response
Note over AuthServer: refresh endpoint should be<br/>exposed internally and should<br/>NOT be available to be<br/>accessed directly by the client
AuthServer->>Client: New access token<br/>(+ optionally new refresh token)
Note over Client: Client stores new tokens<br/>in secure storage
Client->>ResourceServer: Request with new access token<br/>Authorization: Bearer [new_token]
ResourceServer-->>AuthServer: Verify token from Auth server
AuthServer-->>ResourceServer: Token verification response
ResourceServer->>Client: Response<br/>(data)<br/>status: 200 OK
Note over AuthServer, RefreshStore: Refresh token rotation:<br/>Old refresh token invalidated<br/>when new tokens are issued
This project is intended for educational purposes and as a starting point for more robust authentication implementations.