Skip to content

Conversation

@kaspernowak
Copy link
Contributor

Overview

This PR introduces full OAuth2 Authorization Code Grant and OpenID Connect support to the Laravel Bexio package, along with secure encrypted token storage, centralized OAuth handling, and updated documentation.

🔑 Key Features

✅ OAuth2 Support

  • Implements OAuth2 Authorization Code Grant with refresh token handling.
  • Adds OpenID Connect userinfo verification for enhanced identity validation.
  • Introduces new support classes:
    • BexioOAuthController, BexioOAuthService, BexioOAuthTokenStore
    • FetchUserinfoRequest, UserinfoDTO, UserinfoVerificationException
    • Centralized exception handling and view rendering for OAuth flows

🧱 Integration with Existing Package

  • BexioConnector now supports both PAT and OAuth2 modes
    • Uses Saloon’s AuthorizationCodeGrant and AlwaysThrowOnErrors traits
    • Loads OAuth2 configuration from config/bexio.php
  • BexioServiceProvider registers views and routes for the new OAuth2 flow

🔐 Secure Token Storage

  • OAuth2 tokens are encrypted using Laravel’s Crypt facade
  • Replaces BexioTokenStore with BexioOAuthTokenStore

📚 Documentation

  • Expands README.md with:
    • OAuth2 flow and setup
    • Environment and config examples
    • Token storage behavior
    • Migration guide from PAT to OAuth2
    • Clarifies mutual exclusivity of PAT and OAuth2 modes

🧪 Tests

  • Adds tests for:
    • Token storage (BexioOAuthTokenStore)
    • OAuth flow (BexioOAuthController)
    • OpenID userinfo handling
    • Service logic (BexioOAuthService)

Covers:

  • Redirects, callbacks, exceptions, token persistence
  • Email verification and failure scenarios

🛠 Migration Notes

  • Only one authentication method (PAT or OAuth2) should be enabled.
  • See the updated README.md for full instructions.

Closes #25

@kaspernowak kaspernowak changed the title Add Secure OAuth2 Support, Encrypted Token Storage, and Documentation Improvements Add OAuth2 Support Jun 12, 2025
@kaspernowak kaspernowak force-pushed the feature/implement-oauth2-support branch from 7618077 to ab3e88b Compare June 12, 2025 15:37
@kaspernowak
Copy link
Contributor Author

Hey @StanBarrows! My force push closed this PR. Are you able to reopen it, or should I create a new PR?

@StanBarrows StanBarrows reopened this Jun 13, 2025
@StanBarrows StanBarrows requested a review from RhysLees June 13, 2025 09:30
@StanBarrows StanBarrows requested review from StanBarrows and removed request for RhysLees June 13, 2025 09:30
@StanBarrows StanBarrows added the enhancement New feature or request label Jun 13, 2025
@StanBarrows
Copy link
Contributor

@RhysLees

Could you take a closer look at this?

The new Bexio credentials for the dev setup are available in our 1Password instance.


return [
'auth' => [
'use_oauth2' => env('BEXIO_USE_OAUTH2', false),
Copy link
Contributor

@StanBarrows StanBarrows Jun 13, 2025

Choose a reason for hiding this comment

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

@RhysLees

Could you strictly separate the token-based and OAuth2 approaches?

At the moment, we’re just passing the token like this to create a new connector:

new BexioConnector(token: $this->user->bexio_api_key);

I think it might be better to create a separate OAuth2Connector instead of combining everything into one connector. This also guarantees 100% that we won’t break any existing applications.

Also, please adjust the format of config/bexio.php to the following:

<?php

return [
    'auth' => [
        'token' => env('BEXIO_API_TOKEN'),

        'oauth2' => [
            'client_id' => env('BEXIO_OAUTH2_CLIENT_ID'),
            'client_secret' => env('BEXIO_OAUTH2_CLIENT_SECRET'),
            'email' => env('BEXIO_OAUTH2_EMAIL'),
            'scopes' => [],
        ],
    ],

    'route_prefix' => 'bexio',
];

Copy link
Contributor Author

@kaspernowak kaspernowak Jun 13, 2025

Choose a reason for hiding this comment

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

@StanBarrows We could create a separate OAuth2Connector, but I would not worry about backwards compatibility, as the current BexioConnector can only use one approach or the other. So if the laravel app uses a PAT, then the OAuth2 logic will never be touched, and vice versa.

One thing that would be of a concern to me, would be if you would use OAuth2 on a per user basis, like in your example:

new BexioConnector(token: $this->user->bexio_api_key);

As the default setup of the PR assumes that the app only stores one authenticator (refresh token + access token + expiry data) globally per app in cache, and the only account that is authorised to create the authenticator, is the one defined with the email in config as BEXIO_OAUTH2_EMAIL. So if this package should also be used as a per user basis, the architecture needs to be adjusted. Key points to consider:

  1. Which bexio accounts should the app be able to generate an authenticator for, with the used Client ID and Client Secret?
  2. Should we support multiple authenticator storage methods for the consuming Laravel apps (cache, file, database), or should we default to encrypted cache storage and let the consuming app customise their storage logic by extending/override the BexioOauthTokenStorage class?

Regardless of the outcome, I think keeping everything to one connector, reduces the migration barrier by consuming apps, as they would only need to think about setting the .env variables and add the scopes to the bexio config that they need for the endpoints they use. Everything with connecting to bexio would then remain identical to how it was before.

Could you give me an example of a company or app, where multiple app users should have access tokens for bexio?

Copy link
Contributor

@StanBarrows StanBarrows Jun 15, 2025

Choose a reason for hiding this comment

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

@kaspernowak

In our app, we need to connect to multiple Bexio instances per application. We have a typical SaaS application where each tenant connects to their own Bexio instance. That’s why the approach in the PR would not work for us at the moment. So, in order to get this merged, I need to ensure that the approach works for our at least our application.

We already created different packages — for example, laravel-docuware — where we handle OAuth dynamically, but refactoring this requires time. I’ve planned to work on this as soon as we have some capacity left, ideally within July.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@StanBarrows Thanks for explaining the multi-tenancy requirement. I have a few ideas on how we could implement this:

  1. Token Storage

    • Abstract the token storage mechanism to support different storage backends
    • Let consuming apps choose between cache (simple apps) or database storage (multi-tenant)
    • Allow apps to implement custom storage solutions if needed
  2. Multi-tenant Support

    • Add tenant context to token operations
    • Make the OAuth flow tenant-aware
    • Support tenant-specific configurations
    • Keep the current single-tenant approach as default for simpler apps
  3. Configuration Flexibility

    • Make email verification optional/customizable
    • Allow tenant-specific OAuth settings
    • Support custom success/error redirect flows

This approach would maintain backward compatibility while adding support for multi tennancy.

What are your thoughts on this? I could implement this on the current PR if you want.

Copy link
Contributor

Choose a reason for hiding this comment

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

@kaspernowak

Thanks for your message.

As we’ve already dealt with similar tenant workflows, I’d like to keep things consistent with our existing packages, where we use tenant-aware OAuth 2.0.

I’ve assigned @RhysLees to this task. He’ll look into it as soon as possible — likely not next week, but at the latest the week after.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@StanBarrows Alright, I will let @RhysLees take over from here. Or if you want this packages OAuth2 approach to be consistent with laravel-docuware, then I could also look into implementing it like that, and let @RhysLees review it when he is back?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@StanBarrows Is there an update on this?

Route::middleware(['web'])->prefix(config('bexio.route_prefix', 'bexio'))->group(function () {
Route::get('/oauth/redirect', [BexioOAuthController::class, 'redirect'])->name('bexio.oauth.redirect');
Route::get('/oauth/callback', [BexioOAuthController::class, 'callback'])->name('bexio.oauth.callback');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Route::middleware(['web'])->prefix(config('bexio.route_prefix', 'bexio'))->group(function () {
    Route::get('oauth/redirect', [BexioOAuthController::class, 'redirect'])->name('bexio.oauth.redirect');
    Route::get('oauth/callback', [BexioOAuthController::class, 'callback'])->name('bexio.oauth.callback');
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been fixed in the latest commit.

__DIR__.'/Http/Controllers/BexioOAuthController.php' => app_path('Http/Controllers/BexioOAuthController.php'),
], 'bexio-controller');
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@RhysLees

        // Issue Temporary Fix:
        // https://github.com/spatie/laravel-data/pull/699#issuecomment-1995546874
        // https://github.com/spatie/laravel-data/issues/731

@kaspernowak
Copy link
Contributor Author

Hi @StanBarrows, @RhysLees,

I’ve pushed some changes that implement multi-tenancy support, and I tried to make this PR aligned more with the conventions of laravel-docuware. I prioritised backward compatibility as well, so I kept the ability to pass token in the BexioConnector constructor.

I had to introduce a config resolver pattern to enable multi-tenancy for the OAuth2 redirect/callback flow, which allows for setting the configuration dynamically in any service provider in the consuming apps.

I wanted to get these changes in before the next review cycle so the PR is as close as possible to your SaaS requirements.

@StanBarrows
Copy link
Contributor

Closing this for.
#36

@StanBarrows StanBarrows closed this Jul 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: OAuth2 Support

3 participants