I feel the pain of anyone working with OAuth for the first time: with an RFC (6749) of 75 pages - written in a niche vernacular of "tokens", "grants", and "scopes" - it can seem quite daunting when you're first introduced to it.
The good news is that if you can remember a few simple principles, it becomes quite a simple technology to work with. The even better news? Most of these principles are pretty much common sense, like number one...
Your client secret is called a 'secret' for a reason: hide it!
Let's start with one of the most obvious - but similarly one of the most important - points: the
client secret need to be kept secret. It's not to be stored in your desktop binary, nor is it to be stored in your mobile application, and it's certainly not to be stored in your front-end Single Page Application!
If you find yourself placing your client secret in any of those locations, then you're undoubtedly doing something wrong; i.e a Bad Idea™. The type of OAuth flow that most people are familiar with - the browser redirect based "Authorization Code" Grant - requires the client secret to be used; and as a result, some developers naturally try and place the client secret in user-accessible areas.. this is wrong, and entirely insecure.
Your client secret needs to live in places that only you can access, such as a back-end web service.
Terminology matters: your client is not the OAuth client
The client secret often finds it's way in to the wrong location based upon developer confusion over what the client actually is. The OAuth spec makes this pretty clear:
An application [i.e back-end service] making protected resource requests [i.e API requests] on behalf of the resource owner [i.e end user] and with its authorization. (Section 1.1)
For instance; say you build a service that integrates with Twitter. You have a server-side component that monitors a user's mentions and builds analytics, and a client-side application that displays those statistics. Your client is still the server-side component, as that's the part of your architecture that communicates with the OAuth server.
So if you're integrating with a third party service, there's a good chance that the OAuth client is actually your server.
Remember your Refresh Tokens; they're just as important as Access Tokens.
You need to store your refresh tokens and associated expiration times; and you need to ensure you've implemented the correct mechanism to utilise these tokens, allowing you to retrieve fresh access tokens.
Sounds obvious, right? You'd be surprised at a few of the naive implementations - often in-house - that don't deal correctly with token renewal, and subsequently require new authorisations via the redirect flow. Not only is this poor UX, but it's also a major source of frustration if token expires during a long running task!
As the OAuth server authenticates both the client and the user during the initial generation of the Access Token, renewal via the Refresh Token doesn't need to be as convulated; in fact, token renewal is as simple as hitting one endpoint. (RFC6749 - Section 6)
Tokens can be refreshed as and when needed - i.e upon the OAuth server returning an error as you've tried to use an expired token - or automatically if the ToS (terms of service) of the third party allows. A trivial mechanism is to have a batch job (i.e via cron) that runs every x hours, and refreshes tokens due to expire in the next x hours.
Multiple environments? Multiple clients, please.
When using the common "Authorization Code" Grant OAuth flow, the server authenticates your application by issuing the user's browser a redirect back to it; this is a mechanism that ensures only a client located at the correct
redirect_uri can utilise your client credentials. This leads to an obvious problem if you're running multiple instances of your client, each available at different URLs.
For instance; a fairly common set-up when developing web services is to have multiple environments - such as
dev.my-awesome-app. If you have set your OAuth client's
redirect_uri to something like
www.my-awesome.app/auth-redirect, then neither
dev.. will be able to utilise the same OAuth client credentials.
This limitation doesn't limit previously generated tokens though; so you could take the access token (and refresh token) for a given user, and manually use them on your alternative environment. A solution which, may sound tempting for debugging/development purposes... but that too has a flaw - and should be considered a Bad Idea™.
www.my-awesome.app generates an access token and a refresh token for User A, using Client ID 123. You manually copy this token pair to
test.my..., and proceed to experiment with it. Eventually the access token comes close to it's expiry time, so
test.my... runs the code path to refresh it.
www.my... now holds an expired access token, and a refresh token that's been invalidated due to prior use: the user is effectively locked out until they go via the entire auth flow again.
This happens because a user is only going to have one set of tokens for any given client, and refresh tokens can't be used multiple times. The only real solution is to register a client per environment, in which case you no longer need to be concerned with token synchronisation issues.
There are multiple authentication "grants" (i.e "flows")
If you've made it this far then you've probably recognised me discuss the "usual" OAuth flow - i.e whereby the user's browser is redirected - and you may be curious as to how a mobile app, or a single page app, is supposed to authenticate via OAuth.
There are multiple authentication "flows" though, and these are dictated by what the type of "Authentication Grant" you decide to use.
An authorization grant is a credential representing the resource owner's authorization (to access its protected resources) used by the client to obtain an access token. This specification defines four grant types -- authorization code, implicit, resource owner password credentials, and client credentials -- [...].(Section 1.3)
Picking the correct grant to use in your application is imperative; not only does it have a UX impact, it's also important from a security point of view. Each grant type makes certains assumptions - i.e about whether the client secret is available, or whether HTTP redirects are possible - which in turn provides quite a lot of flexibility with regards to API consumption from different platforms.
Auth0 - best described as 'Authentication-as-a-Service' - have produced a great resource aimed at detailing how to choose the correct grant type. It's available here - and even contains a useful little flow-chart!
By using the correct grant type, you can even authenticate securely from clients that are deployed to targets controlled by the end-user - i.e mobile phones, web browsers, or desktop applications - by forgoing the need to utilise the
It's actually quite simple.
Above all, OAuth is actually surprisingly simple; I'll concede that it appears convulated at first, and the terminology can be quite archaic. However, if it didn't possess a degree of simplicity and ease of use, it would never have reached the adoption level it currently has.
As a provider of an OAuth server, you're capable of validating both the client and the identity of the associated user; a very good combination. Meanwhile, as a consumer of an OAuth service - a client - you can rely upon a standard mechanism for user authentication, as well as integrate multiple identity providers (i.e Github, Twitter and Google) with a single protocol.
To use OAuth competently you don't need to understand the entire 75 pages of the spec - hell, you don't even need to read all 75 pages! Having a basic high level understanding of how OAuth works (and the different grant types) - combined with knowing where the spec is if you need a "source of truth" - is enough to get you started.