Skip to content

feat: v2 auth passport cross tenant - #567

Draft
nikola-maric-aula wants to merge 103 commits into
mainfrom
feat/v2-auth-passport-cross-tenant
Draft

feat: v2 auth passport cross tenant#567
nikola-maric-aula wants to merge 103 commits into
mainfrom
feat/v2-auth-passport-cross-tenant

Conversation

@nikola-maric-aula

@nikola-maric-aula nikola-maric-aula commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Context

In BE v1 we use custom JWT for auth. The new Access+Refresh tokens follow OAuth2.0 spec and are using industry-standard Laravel Passport library. It comes with full revocation, expiry, purging, refreshing and other standardized behavior.

Frontend needs to adapt to using this v2 authN, but since it's standardized approach, it should be simple by using some OAuth2.0 library.

Implementation details

  • Laravel Passport uses RS256 for signing - it has asymmetric keys. Old BEv1 used HS512, symmetric but larger key means slower encrypt/decrypt. Legacy code has been updated to support using oauth-public.key from BEv2 for verifying v2 JWTs.
  • Laravel Passport is using our custom CentralClient instead of the default PassportClient. Our CentralClient is fixed to work with the central (manager) database, while all other operations over the database during authN (like checking User credentials) happen in multi-tenancy-context of a single tenant. This enables the Mobile Apps to use a single shared Client for all Tenants. The HTTP Header "aula-instance-code" still determines the Tenant for logging in the user.

TODOs

Example usage

edit your .env file and update the variable DB_CONNECTION=mariadb_central (same as in the new .env.example)

inside the running v2 container (truncated the output for conciseness):

/opt/laravel # php artisan db:seed
Client ID:     019fd710-77ac-72ce-8278-5597369cef77
Client Secret: N/A
/opt/laravel # php artisan tenant:add-user
 Username:
 > b
 Select role [Admin (50)]:
  [0] Guest (10)
  ...
  [9] Tech Admin (60)
 > 0

=== Summary ===
+-----------+---------------+
| Field     | Value         |
+-----------+---------------+
| Tenant    | E2E.0 (db000) |
| Username  | b             |
| Full Name | b             |
| Email     | b             |
| Role      | Guest (10)    |
+-----------+---------------+

User 'b' created successfully as Guest.

/opt/laravel # php artisan tenant:add-user
=== Summary ===
+-----------+---------------+
| Field     | Value         |
+-----------+---------------+
| Tenant    | E2E.0 (db000) |
| Username  | c             |
| Full Name | c             |
| Email     | c             |
| Role      | Admin (50)    |
+-----------+---------------+

User 'c' created successfully as Admin.

Then attempt using access token of Guest user:

λ › curl localhost:8080/api/v2/oauth/token -XPOST --data 'grant_type=password&client_id=019fd710-77ac-72ce-8278-5597369cef77&username=b&password=abc123' -H 'Accept: application/json' -H 'aula-instance-code: db000' 
{"token_type":"Bearer","expires_in":60,"access_token":"ACCESS_TOKEN_B"}
λ › curl localhost:8080/api/v2/users -H 'Accept: application/json' -H 'aula-instance-code: db000' -H 'Authorization: Bearer ACCESS_TOKEN_B'
{
    "message": "This action is unauthorized.",
    "exception": "Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException",
...
}

Then using access token of Admin user:

λ › curl localhost:8080/api/v2/oauth/token -XPOST --data 'grant_type=password&client_id=019fd710-77ac-72ce-8278-5597369cef77&username=c&password=abc123' -H 'Accept: application/json' -H 'aula-instance-code: db000' 
{"token_type":"Bearer","expires_in":60,"access_token":"ACCESS_TOKEN_C","refresh_token":"REFRESH_TOKEN_C"}
λ › curl localhost:8080/api/v2/users -H 'Accept: application/json' -H 'aula-instance-code: db000' -H 'Authorization: Bearer ACCESS_TOKEN_C'
[{"publicId":"PNVb699Z5Yg9f6UCAgGYOSmyCAOa6fjc","displayName":"Admin User","userName":"admin","realName":"Admin User","email":"admin@aula.de","userLevel":50,"aboutMe":null,"createdAt":"2026-08-04T10:29:00+00:00","updatedAt":"2026-08-04T10:32:25+00:00","status":1},{"publicId":"qIVcx4ZJ12uNOiQ1BYlROyp0tdnwwt8v","displayName":"Tech Admin","userName":"tech_admin","realName":"Tech Admin","email":"tech@aula.de","userLevel":50,"aboutMe":null,"createdAt":"2026-08-04T10:29:01+00:00","updatedAt":"2026-08-04T10:29:01+00:00","status":1},{"publicId":"MMBBtxpxMOP4lYEaPR6KqrCQuxXlhrEr","displayName":"a","userName":"a","realName":"a","email":"a","userLevel":50,"aboutMe":null,"createdAt":"2026-08-06T12:22:15+00:00","updatedAt":"2026-08-06T12:22:15+00:00","status":1},{"publicId":"QMtVd9yX71gAwHFud1erbdr9GZaxisYM","displayName":"b","userName":"b","realName":"b","email":"b","userLevel":10,"aboutMe":null,"createdAt":"2026-08-06T12:34:11+00:00","updatedAt":"2026-08-06T12:34:11+00:00","status":1},{"publicId":"m6yARWiNqpCAPP28g1v5whFincTRvaTB","displayName":"c","userName":"c","realName":"c","email":"c","userLevel":50,"aboutMe":null,"createdAt":"2026-08-06T12:35:33+00:00","updatedAt":"2026-08-06T12:35:33+00:00","status":1}]

Then using refresh token of Admin user:

λ › curl localhost:8080/api/v2/oauth/token -XPOST --data 'grant_type=refresh_token&client_id=019fd710-77ac-72ce-8278-5597369cef77&refresh_token=REFRESH_TOKEN_C' -H 'Accept: application/json' -H 'aula-instance-code: db000'
{"token_type":"Bearer","expires_in":60,"access_token":"ACCESS_TOKEN_C_2","refresh_token":"REFRESH_TOKEN_C_2"}

λ › curl localhost:8080/api/v2/oauth/token -XPOST --data 'grant_type=refresh_token&client_id=019fd710-77ac-72ce-8278-5597369cef77&refresh_token=REFRESH_TOKEN_C' -H 'Accept: application/json' -H 'aula-instance-code: db000'
{"error":"invalid_grant","error_description":"The refresh token is invalid.","hint":"Token has been revoked"}

If we now try to use the old access token of Admin user, it has been revoked:

λ › curl localhost:8080/api/v2/users -H 'Accept: application/json' -H 'aula-instance-code: db000' -H 'Authorization: Bearer ACCESS_TOKEN_C'
{"message":"Unauthenticated."}

But the new access token works.

Checklist

  • Tested manually
  • GitHub issue linked
  • Changelist updated
  • Backward and forward compatible with aula-frontend/releases
    • it is not breaking existing FE because current version of FE is not using this authN
  • Independent of the other BE version (v1 <-> v2)
    • BEv1 can interpret the JWT signed with new BEv2 code
  • Must be deployed ASAP (HOTFIX)
  • Needs update of docs.aula.de (repo)

@nikola-maric-aula
nikola-maric-aula force-pushed the feat/v2-auth-passport-cross-tenant branch from 0cac83c to d0e4c11 Compare August 6, 2026 14:08
@nikola-maric-aula
nikola-maric-aula changed the base branch from main to v2-user-abstract-nikola August 6, 2026 16:01
@nikola-maric-aula nikola-maric-aula linked an issue Aug 6, 2026 that may be closed by this pull request
@nikola-maric-aula nikola-maric-aula mentioned this pull request Aug 10, 2026
7 tasks
Base automatically changed from v2-user-abstract-nikola to main August 11, 2026 15:57
$driver = Socialite::driver('keycloak');
/** @var SocialiteOAuth2User $socialiteUser */
$socialiteUser = $driver->stateless()->user();
$socialiteUser = Socialite::driver('keycloak')->stateless()->user();
@nikola-maric-aula
nikola-maric-aula force-pushed the feat/v2-auth-passport-cross-tenant branch from 955b1f0 to 9ba3d36 Compare August 13, 2026 15:15
Comment thread Makefile
xdebug-v2-docker-setup:
docker compose exec aula-backend.v2 true
docker compose exec aula-backend.v2 sh -c 'apk add php84-pecl-xdebug; echo -e "zend_extension=/usr/lib/php84/modules/xdebug.so\nxdebug.mode=develop,debug\nxdebug.start_with_request=yes\nxdebug.client_host=host.docker.internal\nxdebug.client_port=9013\ndefault_socket_timeout=600" > /usr/local/etc/php/conf.d/docker-xdebug.ini'
docker compose exec aula-backend.v2 sh -c 'composer require --dev -m phpunit/phpunit ^12'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work for me ootb because post-update-cmd fails to write to .ide_helpers. I think IDE helpers within the container don't make much sense; maybe move it from implicit cmd to an explicit composer script to be run on demand (as well as depending on dev's IDE needs)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v2+legacy interoperable authentication

4 participants