Open eliasmalik opened 4 years ago
Whether or not the server- or client-side solution is chosen, there'll need to be some new endpoints on the backend. It's not clear that there's a benefit to versioning these: OAuth is a fixed standard, it's not going to change. So these endpoints probably don't need to be nested under /v1/...
and can instead live at something like /oauth/...
.
In addition, despite the fact that Eventbrite is the only integration currently being considered, every OAuth-integration will be structured similarly, so it's worth designing a solution that can easily accommodate new integrations, if and when they are desired.
In order to make this flexible, it's probably worth identifying the 3rd party service in the URI so new services can be accommodated simply:
/oauth/{service}/redirect?code=AUTH_CODE&target=TARGET_URL
i.e.
/oauth/eventbrite/redirect?code=AUTH_CODE&target=TARGET_URL
There's several common operations that need to be done in the handler for this URI:
It may not be worth doing during the first integration, but if/when further ones are required, these operations can be pulled into an Eventbrite
module that has a common interface as all other OAuth modules (example below). This way, the appropriate module can be selected dynamically based on the path parameter {service}
, but the method calls will all be the same.
interface OAuthService {
fetchAccessToken (code: string): Promise<string>;
saveAccessToken (token: string, user: User): Promise<void>;
getRedirectUrl (request: Hapi.Request): string;
}
In this case we'll need an endpoint that's used to get the access token given an authorization code. This can be a simple authenticated endpoint that requires the auth code as a payload parameter. Again, we'll probably want to encode the service in the URL in a similar manner to above (e.g. POST /oauth/{service}/code
), and again, the same 3 common operations identified above will need to be performed in this endpoint.
In this case we'll need an endpoint that's used to store the access token, which can be a simple authenticated endpoint that requires the token as a payload parameter. Again, we'll probably want to encode the service in the URL in a similar manner to above (e.g. POST /oauth/{service}/tokens
). In contrast to the other two solutions, this endpoint will only need to store the token for later use.
In addition, if it is elected not to use a proxy in this solution, we'll need another endpoint to return the access token to the client.
In all solutions there needs to be endpoints for the client to be able to fetch the client ID, and potentially also the redirect URI if using the server-side URI.
In server-side solutions, Eventbrite data requests should be proxied through the API server. There are plenty of reverse proxy libraries available, including the hapi-provided h2o2. This can be structured so that any requests to /oauth/{service}/{*param}
are forwarded to that services base-URL (e.g. /oauth/eventbrite/users/me
is forwarded to https://eventbriteapi.com/v3/users/me
). This forwarded request also needs to include the access token in the authorization header (as specified by the eventbrite API docs). This can be done using h2o2's mapUri
option.
Client-side solutions can also use a proxy, or use the access token to make API calls directly from the client.
As per the authentication instructions on the Eventbrite API docs, there are two options for OAuth: server-side and client-side. The latter is simpler, but the former is recommended by Eventbrite because it doesn't expose the user's API token in the URL (how significant an issue this is is up for debate: probably depends on whether the user/CB has any personally identifiable information on their Eventbrite account). This issue briefly describes a way to implement both solutions. The choice between them is a trade-off that can be made at a later date.
Server side
We need a redirect URI, which is to say a URL that Eventbrite can redirect clients to after they've authenticated them.
Two options for this:
Client-side redirect URI
The client then needs to capture the authorization code from the query parameter, and send a request to the API server so it can continue the process of requesting the users access token by calling
POST https://eventbrite.com/oauth/token
.The API server will then receive the access token in the response from Eventbrite, which it can store in the DB (along with a refresh token, if present), and send a successful response back to the client.
The client can then enable functionality that relies on the Eventbrite integration, although data requests should probably be proxied through the API server since those requests require the access token, which shouldn't be sent to the client (otherwise server-side authorization is pointless).
Server-side redirect URI
The endpoint needs to encode the originating app or URL (so we know where to redirect the client back to after we're done). This can be done either as a URL parameter (e.g.
/oauth/visitor-app/redirect
) or as a query parameter (e.g./oauth/redirect?target=visitor-app
or/oauth/redirect?target=https://visitor.twine-together.com/admin/settings
URL encoded).Once the client is redirected to (for example)
https://api.twine-together.com/oauth/redirect?target=TARGET_URL&code=AUTH_CODE
, the API server completes the process of requesting the access token as above, which it again can store in the DB.The client continues as above.
Note: this solution requires the cookie settings to be updated to use
isSameSite: 'Lax'
because we want the cookie to be sent when returning to the domain from a third party domain.Client side
In this mode, the client receives the access token directly from Eventbrite, which then needs to be sent on to the API server so it can be stored and used later on.
After this is done, we have two options:
Option one reduces the number of times the access token is exposed to the client to one per-authentication. Option two potentially reduces the amount of complexity added to the API server by removing the need for a proxy (although, depending on how this proxy is set-up, this may well be fairly minimal).