I’ve built an SPA which uses RaiderIO APIs to fetch character data.
This is hosted using only static website hosting with no server-side components, it just uses fetch()
with CORS. This is great:
- my serving infrastructure is really simple, it’s basically an object storage bucket
- my server never has access to users’ data (even temporarily), and a technically-adept user could verify this
I’m looking to switch to Battlenet WoW Profile APIs instead, and make sure that the server side still doesn’t have access to users’ data or need to build out infrastructure.
Battlenet authentication and Profile APIs don’t seem to apply any CORS restrictions, so once my SPA has an access token, it should all just work – the problem is getting that token.
While I could use an Authorization Code flow, the docs seem to assume the client secret will always be secret. There are scary warnings on this forum that it must always be secret – indicating that only confidential clients are considered.
Keeping this a secret is impossible with public clients, such as a client app or an SPA (where the authorization flow is completed by JavaScript running in the user’s browser). This leaves it vulnerable to hijacking issues outlined in RFC 7636 (the PKCE RFC).
This doesn’t mean public clients are “less secure” than confidential clients – they’re all well defined in the OAuth 2.0 RFCs.
This issue come up a few times before:
- PKCE Flow for Single Page Application (No server) or Native API access
- Web client or serverless OAuth flow
- Please allow mobile deep link redirect urls
Blizzard reps’ (and others’) responses to date have been “make a server-side component and proxy it” (ie: switch to being a confidential client instead) and that public clients shouldn’t exist, but this would mean the server side would start handling user data, I’d need to build out more infrastructure to support it, and deal with all the privacy and data protection implications.
Because my SPA is entirely stateless and doesn’t use the access token for authentication or identity, where an access token came from doesn’t really matter. It’s only used by the SPA to call Profile APIs on the user’s behalf, so isn’t a security risk to any of its functionality.
If the SPA generated the code challenge on the client side and stashed it in session storage (not cookies), Battlenet supporting PKCE would prevent:
- a malicious app on a user’s device from using an authorization code intended for my SPA (potentially allowing access to protected data) to access Battlenet APIs directly
- someone that could read my web server’s request logs from using the authorization code (passed as a query parameter in the redirect response) to request an access token and access Battlenet APIs directly
Only the first issue would be addressed by switching to a confidential client model, but it would make the scope of the second issue far wider.
On that basis, me shipping the client secret in an SPA is the lesser of two evils. Though for others, your mileage may vary – it really depends how you’re using the APIs.
PS: Even for confidential clients (server-side apps), PKCE is recommended in the OAuth 2.0 Security BCP, and is required in the current OAuth 2.1 drafts – so this doesn’t magically go away.
PPS: SimulationCraft has shipped binaries with an embedded Battlenet API client secret for many years.