Comments (10)
Awesome, I think I might go for this then, here's a class style I've come up with:
class ForgeKeychain implements SaloonKeychain
{
public function __construct(
protected string $apiKey
)
{};
public function default(): self
{
return new static(config('services.forge.key'));
}
public function toSaloon(SaloonRequest $request): void
{
// The keychain could have the helper methods like
// addToken, addBasicAuth, addDigestAuth rather than the
// request.
$request->addHeader('Authentication', 'Bearer ' . $this->apiKey);
}
}
I'm thinking of moving the authentication helpers like withToken to this keychain class in the form of a trait so it's a shortcut to add a bearer token rather than it being on the request and breaking the Saloon pattern
from saloon.
Thanks so much for writing this up @WalrusSoup. I think you have a really good point about how using "withToken" breaks out of Saloon's pattern of everything being wrapped in objects. I released it because I feel for some people might not be able to easily pass in a token. Maybe I should add it as an optional add-on trait rather than bolting it onto every request?
After seeing it from this angle I can see it actually adds a bit of confusion.
I like your DTO approach to this though. I have done this too on an API wrapper I built around Saloon with Oauth 2. Essentially I have a "Keychain" DTO which is returned after successfully generating authentication tokens, see here:
Here's the Keychain class:
https://github.com/Sammyjo20/intelligent-office-php-sdk/blob/main/src/Helpers/Keychain.php
Which looks very similar to what you are doing, but I would be quite happy to add this to do the docs with maybe a few tweaks inspired by my approach too.
from saloon.
I love that keychain approach (and the name, makes so much sense!).
One reason I used the approach inside the wrapper was it would act as a class property singleton. Requests new up a connector each time, which could be some load in unexpected places depending on where the dev gets their token.
In the case of Salesforce, Zoom, etc where requests are often paged
it could mean unnecessarily contacting the database layer. While this is a small concern, it was enough to make me think optimization could be helpful.
I could see an approach where a Keychain feature built into actual library could be really helpful. I suppose the thing to ask then would be where does Saloon stop as far as scope? I think a keychain is probably as far as it should go.
Perhaps a class based approach built into the connector may be consistent with how Saloon is built out so far?
class MySeviceKeychain extends SaloonKeychain
{
// this can be in parent class
protected static $instance;
// could potentially have traits in here for defaults, useBearerHeader, etc.
// called if $instance was not built yet, user should write this function themselves
public function buildKeychain() : MySeviceKeychain
{
// return whatever $instance should be? In the case of Authorization:bearer, just set return new self() with a token?
return MyBackendFunction::loadCredentialsFromDatabaseAndDoStuff();
}
// user could code this themselves or this apply function can yield to the included trait (like Authorization: Bearer)
public function apply(SaloonConnector $connector): void
{
// I should do my own logic here if i need something super custom
}
}
Then over in the actual connector:
protected ?string $keychain = MySeviceKeychain::class;
Then I suppose if there is a keychain set in the connector, the connector can call build
and then a built in apply or the a user supplied apply function.
Just a thought, though. In practice, not sure how maintainable this would be long term.
from saloon.
Glad you like the idea! I’ve now built two SDKs with Saloon using this approach, you authenticate the SDK with
‘$sdk->authenticate($keychain);’
Which loads it into the SDK to use later in your connector.
To be honest though, I see it being really useful as a way you can nicely transport credentials around the application, especially with your example where you might have different sources. What I did for one of my SDKs is I have a Laravel Model which has a method called “toKeychain” which returns its credentials as a SDK keychain that can be passed in. Maybe the same approach can be used for Saloon too, or like you’ve described - you can specify how a keychain is populated inside.
After you have created your keychain, you could pass it into your request or connector.
What would be nice is to let the user handle how the keychain should be processed, because different APIs might require different authentication/headers. Do you have any suggestions for this?
from saloon.
On a side note, I’m definitely interested in building a nice “Oauth” wrapper around Saloon that people can really easily use to connect to an Oauth 2 service. Quite a lot to process on that though so that’s for another day 😅
from saloon.
Ah, I just re-read your message and I like your implementation 👍
I agree with this is probably as far as I would go with Saloon, I don’t want it to be too opinionated but certainly make integrating with APIs easier. This could be a plugin that people add, but I’ll have a think.
Thanks for sparking the thought in me, this is a really cool discussion!
from saloon.
So here’s a first party idea from this discussion
- A user can define a Saloon “Keychain” which a class used to find credentials
- when a connector is booted up, the keychain’s “lookup” method is called (but can be blank)
- If the lookup method is not called the user is expected to pass in a keychain maybe like this:
Request::make()->authenticate($keychain)->send();
or
Request::make()->withKeychain($keychain)->send();
There could also be different types of keychain like:
- BearerKeychain (will prefill the Authorizatoon header)
- BasicAuthKeychain
- Keychain (basic)
from saloon.
withKeychain
would keep the testing consistent with what is in there now, so I like that approach. I also think the three options there would be good too for out of the box customizations without going overboard. As you said, the user would be responsible for handling the logic part of things as it pertains to their implementation and app.
I think the keychain approach also feels very Saloon. This library has a unique style that's very attractive code wise and I think the idea you're coming up with is in line with that.
from saloon.
I have implemented this in v9.0, however I'm having second thoughts. What if instead we just introduced a way to send requests easily from the connector rather than the request.
My initial thought of creating the request and then sending it was to keep it simple but sometimes you might want to create the connector so you can pass in authentication tokens into the connector.
I can see the keychain being useful if you want to authenticate individual requests for a particular user but I'm worried it's complicating Saloon a bit. I think if we kept both it would have to be described nicely in the docs as multiple ways you can authenticate.
What are your thoughts? I've placed this feature in an issue here: #46
from saloon.
Going to mark this as closed since we moved it into another issue 🙇
from saloon.
Related Issues (20)
- Rate Limit Plugin: Issue with 'allow(1)->everySeconds(1)->sleep()' Exceeding One Request per Second
- Inteliphense cannot recognize the merge() method on request HOT 3
- Connector default headers overwrite request body headers HOT 1
- Remove authentication on a specific request HOT 5
- Some properties are removed from the response body
- Using non-standard HTTP methods HOT 2
- Generating Documentation Files for GPT on ChatGPT HOT 4
- Tests using Fixtures with custom Authenticators record incorrect response HOT 1
- async request with multi connector HOT 1
- Allow partial overriding of fixtures HOT 2
- Asserting a request is sent by closure parameter HOT 1
- Connector object() throws an exception but json returns null|object? HOT 1
- Feature Request: Requests have the ability to return a Colleciton HOT 1
- Connector does not support `defaultBody()`? HOT 3
- Request class cannot accept `$query` property in constructor HOT 3
- Possible to Use Multiple Instances of Same Request in Single Test? HOT 3
- Override Connector defaultHeaders() in request HOT 5
- Offloading a Listener for SentSaloonRequest events to a queue returns error HOT 3
- Pagination: `currentPage` doesn't align with `page` HOT 5
- Additional requests with the main request HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from saloon.