Recommendations on secure implementation of OAuth 2.0 (server and client):
-
extra limitations compatible with specification and critical for security are described as required
-
extra features compatible with specification and critical for security are described as required
-
required extensions to specification are marked [EXTENSION]
-
features non-critical for security are marked [OPTIONAL]
-
some optional features are forbidden or required to increase overall control and simplify implementation
Whenever possible all limitations/extensions to specification are described with rationale and/or possible attack scenario.
-
OAuth 2.0 Servers (easy to understand overview similar to this document)
Here is how terms used in this document map to OAuth roles:
- Server
-
authorization server - the server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization (most likely we’ll implement it as a group of authentication and authorization services)
- Client
-
client - an application making protected resource requests on behalf of the resource owner and with its authorization (our own public website is also just a client)
- User
-
resource owner - a person (end-user) capable of granting access to a protected resource
All clients should be registered on the server. Usually server should provide a web interface with these features for owners of client applications:
-
Register.
-
Login.
-
Add/delete his client applications.
-
Edit details about his client applications.
-
Join some of his client applications into a group, to share access provided by user to one of client applications in a group with all others in same group. All clients in a group should share same description (name, url, logo). [OPTIONAL]
-
In case client will leak (occasionally or because it was hacked) many tokens we should provide a feature to allow client to revoke all his current active tokens and change
client_secret
.-
this application type may not have
client_secret
-
it’s usually bad idea to change
client_secret
for desktop/mobile/limited applications because it’s usually hardcoded and thus all users will have to update this application after changingclient_secret
-
-
View names for all
scope
allowed for each of his client applications.
Client details should provide this information:
-
Description (required to let users identify this client):
-
name
-
url
-
logo [OPTIONAL]
-
-
Type of client (it actually just change which ones of other options will be allowed to select, so it’s pure usability feature [OPTIONAL]):
-
server-side web application
-
it’s recommended to require list of IP addresses of servers with this application and allow access only from these IP addresses - to make sure this type of client won’t be used instead of mobile or desktop applications
-
-
browser application (web site)
-
mobile application
-
desktop application
-
limited application (for devices with limited capabilities, like TV)
-
-
List of allowed
redirect_uri
(to protect against many types of attack).-
server-side/browser applications are allowed to use only
https://
-
mobile/desktop applications are allowed to use any url schemes
-
it’s recommended to disallow
http://
andhttps://
except for thelocalhost
-
-
limited applications doesn’t use
redirect_uri
, instead they use non-standard way to getaccess_token
which involve usual browser (either application get thecode
and show it to user so he can then enter this code on server’s website, or server’s website show simple numericcode
to user which usually possible to enter even on limited devices) -
redirect_uri
may contain parameters, but not a fragment
-
-
Allowed
response_type
(needed to avoid lowering security whenever possible and prevent simple intercepting ofaccess_token
).-
server-side applications should not be allowed to choose
"token"
-
each
redirect_uri
must have only one allowedresponse_type
-
-
Is application needs
refresh_token
(needed to avoid lowering security whenever possible).-
not allowed for browser applications
-
in case application need it then this should be handled as extra
scope
- i.e. user should grant it just like all other permissions-
if client details was updated and needs in
refresh_token
was added then it should be handled as asking user to grant extra permissions
-
-
-
Auto-generated
client_id
. -
Auto-generated
client_secret
.-
allowed only for server-side applications
-
-
Is
grant_type=password
is allowed (admin should enable this only for our own clients).
To protect against phishing we should:
-
Block registration of clients with similar names.
-
This can be easily hacked using Unicode symbols, so it’s better to allow only English letters and numbers in client names, remove extra spaces, take in account similar letters/numbers like "I" and "l".
-
-
Disallow using urls with same domain by different client owners.
-
Make sure this client owner really control domain used in url.
-
We can ask him to create unique file on website or send email to postmaster@ this domain.
-
-
If possible, it makes sense to manually pre-moderate added and modified client descriptions.
-
Must support GET and may support POST.
-
If any parameter provided more than once (i.e. it has several values) then return error.
-
Unknown parameters must be ignored.
-
Parameters with empty values must be ignored.
-
-
Must accept
client_id
andclient_secret
usingAuthorization: Basic
(only for clients withclient_secret
) or using POST params (clients withoutclient_secret
should not include this param).-
If both ways are used in same request then return error.
-
response_type
|
|
client_id
|
|
redirect_uri
|
|
scope
|
permissions asked by client
|
state
|
opaque value
|
Reply is HTTP 302 Found
redirect to redirect_uri
with these
parameters appended:
code
|
random value generated by server
|
state
|
equal to parameter |
After parameters we should append empty fragment #
(FIXME or #_=_
- this
value was used by Facebook, maybe just #
is not enough for all browsers
to replace current fragment value, or maybe it’s just a cargo cult).
Note
|
This needed in case our OAuth server is also an OAuth client (for ex. allowing our users to "login with Facebook") - in this case chain of redirects may happens: from Facebook to us with providing user’s auth code/token in fragment, and then from us to our client with providing user’s auth in parameters. If second redirect won’t have fragment then browser will copy fragment part of first redirect into second, thus leaking user’s Facebook auth to our client. |
Reply is HTTP 302 Found
redirect to redirect_uri
with these
parameters appended:
error
|
|
error_description
|
|
error_uri
|
|
state
|
equal to parameter |
response_type
|
|
All other parameters are the same as for grant type "authorization code".
Reply is HTTP 302 Found
redirect to redirect_uri
with these key/value
pairs appended as fragment using same encoding as for url parameters
(key=value&…
):
access_token
|
|
token_type
|
|
expires_in
|
|
state
|
equal to parameter |
-
Must support only POST.
-
If any parameter provided more than once (i.e. it has several values) then return error.
-
Unknown parameters must be ignored.
-
Parameters with empty values must be ignored.
-
-
Must accept
client_id
andclient_secret
usingAuthorization: Basic
or using POST params.-
If both ways are used in same request then return error.
-
grant_type
|
|
code
|
|
redirect_uri
|
|
client_id
|
|
client_secret
|
|
grant_type
|
|
username
|
user’s account name |
password
|
user’s account password |
scope
|
any existing |
client_id
|
|
client_secret
|
|
grant_type
|
|
scope
|
same as or subset of |
client_id
|
|
client_secret
|
|
grant_type
|
|
refresh_token
|
|
scope
|
same as or subset of |
client_id
|
|
client_secret
|
|
Reply is HTTP 200 OK
with headers "Cache-Control: no-store"
, "Pragma:
no-cache"
and json object with these keys as content:
access_token
token_type
|
|
expires_in
|
|
refresh_token
|
|
Reply is HTTP 200 OK
with headers "Cache-Control: no-store"
, "Pragma:
no-cache"
and json object with these keys as content:
error
|
|
error_description
|
|
error_uri
|
|
[OPTIONAL]
To make it possible for client to incrementally ask user for new permissions when they’re needed for functionality just requested by user:
-
usual check is some
scope
granted by user should work completely in background, without asking user to grant thatscope
each time -
client should be able to provide a feature to let user change his decision and grant
scope
which he refused to grant previously -
it should be possible for user to revoke some
scope
he already granted using server’s web interface
Note
|
To implement this Google uses extra parameter for authorization
endpoint: In "OpenID Connect" service Google use optional parameter
One more way to implement this: client may ask user to manually enable
needed In my opinion |
[EXTENSION]
Tokens received by browser applications should be additionally validated to protect against using tokens issued to another user:
-
user U use two applications: usual client A and malicious client B (because it pretend to be some other client or some useful application), and login using his Google account into both applications
-
when U login into B a hacker (owner of B) will get from Google token T for user U
-
next hacker will run client A, it redirects to Google to log in current user, then hacker manually assemble url to client A using
redirect_uri
andstate
just used by A in this redirect and token T - and get access to user’s U account in client A
To protect against this client must check is this token was issued for this client. There are several ways to do this:
-
client may do some request to server using both his
client_id
and thisaccess_token
- if request fails with authentication error then this token was issued for another client -
server may provide special API to get
client_id
tied to givenaccess_token
and client may call it and compare returnedclient_id
with it’s own -
to avoid performance issue because of extra request in two previous cases server may provide extra value together with
access_token
- this value should containclient_id
and it should be signed by server (Google’s OpenID Connect works in this way)
This issue happens because OAuth was designed for authorization, and while it used to access user’s resources on server everything is fine. But when OAuth is misused by client for authentication and as result authorization to user’s resources on client - we got this issue.
For OAuth (not OpenID Connect!) Google provide extra endpoint
/tokeninfo?access_token=
which returns details about given token or
error. Facebook does something similar.
Attempt to reuse authorization code
or use expired refresh_token
is a
sign of a leak. In first case server should revoke all tokens issued using
that code
. In second case server should block access for corresponding
client_id
until that client’s owner log in and get new client_secret
.
We may implement our own library for Javascript, Perl, Python, etc. which implements access to our API (hiding all details of our OAuth 2.0 and JSON RPC 2.0 implementation) which will guarantee protection against some security issues.
Server should be careful to avoid allocating unlimited amount of resources. For example, Google does this: "Limits apply to the number of refresh tokens that are issued per client-user combination, and per user across all clients, and these limits are different. If your application requests enough refresh tokens to go over one of the limits, older refresh tokens stop working.".
-
All sensitive operations (like involving password or scope/consent) must be protected with CSRF token.
-
It shouldn’t be possible to load server web pages (like asking for consent page) inside a frame to protect against clickjacking.
-
Newer browsers support HTTP header
X-Frame-Options
, for older browsers server can use Javascript “frame-busting” techniques. https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
-
-
It may make sense to also reject to load server web pages inside an embedded web view in native mobile app (to force native app to run system browser instead of using embedded web view).
-
The particular techniques for detecting whether the page is being visited in an embedded web view vs the system browser will depend on the platform, but usually involve inspecting the user agent header.
-
-
For clients without
client_secret
server may require PKCE https://www.oauth.com/oauth2-servers/pkce/
-
Client must send and check returned
state
.-
Protects against CSRF. If client provide a feature like "connect your account with Facebook", user is now logged in on client, and user is visiting malicious website, then hacker owning that malicious website may send his own
code
oraccess_token
issued by Facebook to client’sredirect_uri
and get hacker’s Facebook connected to user’s account on client.
-
-
After client has processed parameters sent by server to
redirect_uri
it must redirect to some other internal url to protect against leakingcode
oraccess_token
from current page’s url to any 3rd-party javascript/css/image loaded on returned web page.-
Such sensitive urls on client must avoid loading any 3rd-party javascript/css/image or other resources.
-
-
Client must validate received
access_token
to make sure it was issues to this client.-
This isn’t required in case client use only
response_type=code
and doesn’t supportresponse_type=token
at all).
-
-
It may make sense to always use
response_type=code
, even for client types withoutclient_secret
- this let server to detect leakedcode
and invalidate relatedaccess_token
plus it’ll let both client and server ensure thesecode
andaccess_token
are for thisclient_id
. -
In case client has XSS vulnerability (most client can’t be 100% sure they hasn’t one) some extra protection needed:
-
If hacker can get
client_secret
(desktop/mobile clients, or if he has hacked server-side part of client, etc.) then he can modifystate
to activate CSRF protection on client and have client ignore this request and don’t use issuedcode
- which let hacker to use thatcode
. To protect against this client must usecode
in any case, but then drop receivedaccess_token
if CSRF protection has detected wrongstate
. -
If
state
is stored in some accessible from Javascript place (like in cookies) then it must be an one-time value and must be removed from that place right after receiving a reply - to protect against attempt to get or set it by hacker to bypass CSRF protection.
-
-
All sensitive operations (like "connect with Facebook") must be protected with CSRF token.
-
If server doesn’t have CSRF protection on login page (for ex. Facebook refuse to implement it to not break existing applications), then malicious website can in background login user visiting that site into Facebook account of hacker owning that site. Next it can use "connect your account with Facebook" feature of any 3rd-party client application to connect user’s account on that client to hacker’s Facebook account - also in background, in invisible for user way, when user is on malicious website. As result, hacker will get access to user’s account on that client.
-
It is also recommended to add extra step (after receiving
access_token
but before actually connecting some provider’s account) asking user is he really want to connect it, showing both provider name (Facebook) and name of user’s account on that provider (because there are lots of attacks such as cookie forcing, login/logout CSRF, etc. that can silently relogin user into malicious account on the provider).
-
Note
|
It should be safe to use Google’s OpenID Connect for authentication. |
In general, if client is really bother about it’s own security, it should
test each OAuth server for vulnerabilities before begin using them: is
server allow using redirect_uri
which differs from configured for this
client (for ex. with added extra path, parameters, fragment, using
subdomain like www. or another http/https scheme), is server check
validity of all parameters while exchanging code
to access_token
, etc.