API Security – Securing a SPA’s API When Using External Identity Providers

apioauth2

I'm trying to understand OAuth and Securing APIs better when using External Identity Providers, and all my research on it doesn't really seem to apply to my issue, so I'm starting to wonder if I'm approaching what I'm doing completely wrong at this point.

I have a SPA that is an item manager for a game, it has a custom API (this is also mine) that it gets all of it's data from, such as the items from the game, and saved setups, etc. Users can login to the SPA using their Discord account, which let's them save their setups against their account so they can retrieve them later, among other minor features, that's the main thing.

As the website can be used without login, I was allowing the API to be accessed without any authentication, then adding authentication to it when the user has logged in to give elevated permissions on the API.

My current login flow is:

  1. App performs an Implicit Grant to Discord
  2. Discord Access token returned to App
  3. Discord Access Token is passed to API as a Password Grant, with username discord (I'm sure this is a very bad way to do this, but this is the main reason I'm making this post)
  4. API returns Access Token for accessing the rest of the API

While my current system works, I'm sure it's not the best way to do it, and as I've had requests to make the API open to others to use, I want to make sure this part is all squared off and working well first.

Would appreciate any help anyone can give with organising this mess

Best Answer

Since you're using the implicit grant, the SPA gets the token from Discord and then you have to send it to your (API) server to exchange it for your own access token. There is nothing really wrong with exchanging one token for another one, security wise. It's not really a password grant if you're not sending the user's password. If this fits your needs, you can leave it as it is.

For completeness, I should mention that the implicit flow has some vulnerabilities. To mitigate those, you might consider migrating to the authorization code flow with PKCE, which is for the same use case, but more secure.

If you'd like to streamline things, here are some suggestions.

Accept Discord tokens directly

The simplest approach would be to accept the Discord access token in your API directly instead of issuing your own. Depending on your language and framework, you would intercept the access token on every request, validate it and perform business logic, then return your application user data for the controllers/routes to consume. Basically what you currently do when you get the Discord token, except on every request that needs it. To get the user data back to the SPA, you can expose an endpoint like /user/self that returns any necessary data.

With this approach, your clients can use any of the flows offered by Discord without the token exchange step.

Issue your own tokens

When you need to issue your own tokens, the approach to forwarding authentication to an external provider is typically to use a flow where your server ends up with the access token at the end of it. This can be the authorization code flow or implicit flow with form post. They key is that the server has the tokens at the end of the flow, and can perform business logic before invoking its own authorization mechanism of choice.

You should be able to find tutorials for this for your stack, and maybe even a ready-made Discord client library if you're using a popular language.

Going down this route, you have much more control. But by issuing your own tokens, you're taking on the role of authorization server. So if you need to support additional sign-in flows, like the client credentials flow (for automated sign-ins) then you would need to implement that too.

Related Topic