Hmm. I think there's a lot you don't understand, here. Tons of sites do use OAuth (and often OIDC) for initial authentication, StackExchance (optionally) being one of them.
To answer the closest version of your question that makes sense:
Why don't any sites use pure OAuth without any sessions?
The simple answer is that it would be extremely slow and enormously wasteful. Imagine the performance impact of adding an extra two client-side round trips (one redirecting the client from the resource server - e.g. StackOverflow - to the Authorization server - e.g. Google - and then a second one redirecting it back) plus often a back-end round-trip (to exchange the returned OAuth authorization code for an access token) on every single authenticated request! (Remember that a single web page may have hundreds of authenticated components, such as images, posts, comments, likes/reactions, user styles, user info such as name, and more; many of these are typically sent in their own request/response pairs but are processed in parallel.) For this reason, OAuth resource servers typically use only a single OAuth redirection (at login) and then establish a local session (typically using cookies or a Bearer token) for ongoing access until the session ends.
Furthermore, in its purest form, OAuth is used to grant third parties access to secure data (without giving the third party the credentials for the secure data). That's obviously useful if you're e.g. Office 365 (a Microsoft property) and want to use data from the user's Google Calendar (since Microsoft is a third party distinct from both Google and the user). It's even more useful if the resource server is some relatively untrusted site that you absolutely don't want to give unfettered access to your Google (or Microsoft, or Facebook, or Apple, or...) account. Without OAuth or another way of delegating authorization, you'd have to give that third-party site access to your credentials
However, most websites are only accessing the user's data stored in the website's provider itself (e.g. looking at your GCal on calendar.google.com, or your post on security.stackexchange.com); there is no third party, and thus no point in using OAuth. Why would Google need to ask Google whether to allow you to access your Google calendar? Why would it need to hide your Google [Calendar] credentials from Google [login]? Now, in practice, a company may use OAuth even for first-party authentication, because it provides a convenient (in the sense of "lots of client and server libraries already exist", not in the "adds no dev effort or maintenance complexity") way to transfer authentication status across domains (consider: stackoverflow.com, superuser.com, and stackexchange.com are all owned by the same company and use the same login but can't share cookies or other secret storage easily, although all *.stackexchange.com sites can amongst themselves). But for smaller sites, or sites that really only have one web property, there's just no point.
In practice, OAuth also gets used (by itself, or in combination with other protocols such as OpenID Connect a.k.a. OIDC) for implementing single-sign-on (SSO) with third-party authentication. Every site that has a "Sign in with Google" (or with Apple, or Microsoft, or Facebook, or Github, or Twitter, or...) and isn't a Google (or Apple/Microsoft/Facebook/etc.) site, it using OAuth in this way. Sites may or may not bother with OIDC on top of OAuth when doing this; OIDC is useful if you want to have a single set of profile details shared across multiple sites (so that every site knows if you change your name or whatever) but most of the time, the site that you're using SSO for only needs one unique identifier (typically your email address) and it can get that from OAuth alone and then use that to look up your account in an internal profile database.
Let's try to unpack the rest of your confusion now.
oauth is the natural evolution of authentication protocols
- OAuth isn't exactly an authentication protocol at all, actually. It's a method for delegating authorization.
- This sometimes incidentally carries authentication as well (which technically you're supposed to use OpenID / OIDC for, but in practice the model is usually authorization-based-on-identity so the most succinct way to transmit authorization is just to transmit one uniquely identifying value, and you can do that with OAuth alone). Even OIDC is still just delegating authentication to a third party.
- OAuth is fully dependent upon the existence of some concrete authorization or authentication; whoever you delegate authorization to needs to know whether any particular party is authorized to do something, and that usually boils down to knowing who they are (authentication). As such, OAuth doesn't replace the need for existing authentication protocols, it just moves them.
tokens, at this point you want to separate the front from the back.
oauth,openid connect ... centralized way of handling authentication and having authorization and authentication servers.
You're conflating at least three different (though related) kinds of thing here - secret (or credential) handling and transmission, credential format, session management, delegation - all of which are part of or related to actual authentication. There's not really a natural progression between these items, for a bunch of reasons.
- HTTP Basic (and Digest) Authentication is a way to send credentials in a specific format in order to authenticate (or authorize; they use the
Authorization request header and the username might be blank or not identify a single user) a single request. In theory this is stateless, in keeping with the stateless nature of HTML. In practice, user agents such as browsers will only ask for the credentials once, then remember and submit them automatically on every request to create an unofficial session; getting the client to forget the credentials (end the session) actually requires non-standardized workarounds.
- Modern replacements for HTTP Basic usually involve one or more of submitting a username and password in an HTML form, submitting an identity and then a "one-time password" (typically from an app or sent via email/SMS) also via HTML form, using WebAuthN/FIDO2, using mutual TLS (TLS with client certificates; rare on websites but common for server-to-server communication), or using an API key for the initial authentication. They then may either generate a session (see below) or not, depending on the method selected and the site/service's use case. Typically, things that require user action and/or significant server processing time, like passwords - one-time or otherwise - and WebAuthN, get a session.
- Cookies are a standardized means to work around the statelessness of HTTP, by requiring the client to store and transmit a server- or site-chosen string. In the general sense, they have nothing to do with authentication; all that they are for is storing state on the client in a controllable and automatically-usable way.
- Cookies frequently store stuff that has little or nothing to do with authentication, such as a "remember my username" value, your language and theme of choice, whether you've allowed tracking cookies, and of course tracking cookies themselves for site analytics and/or advertising.
- It is entirely possible - though unwise - to re-implement Basic or Digest auth using cookies, e.g. by having a cookie called
http-authorization with a value of
Basic <Base64(username|password)>. In practice, the difference between this and actual HTTP Basic auth is simply that the lifetime and scope of the credential storage can be specified in a standard manner... and that you'd need some other way (such as an HTML form) to collect the credentials in the first place and store them in the cookie and some server-side code to extract the credentials.
- Cookies are in fact very widely used in real-world OAuth implementations. This is true both on the site that that provides the authorization / identity and the site that uses it after delegating the process (and indeed during the process itself).
- It is not clear what you mean by "tokens" here. A token is a kind of secret that can be used for authentication (a credential), but typically one that is specific to a particular site and not user-memorized.
- JWTs are a kind of token, but so are API keys, random opaque session tokens, refresh tokens (in form, typically identical to opaque session tokens), OAuth state values, OAuth authorization codes, OAuth access tokens (themselves typically either an opaque session token or a JWT), Bearer tokens, Kerberos tickets, anti-CSRF tokens, and many, many more (though these are most of the commonly used ones specifically as part of an authentication/authorization process over HTTP i.e. on the web).
- "JWT", "random opaque token", and "Kerberos ticket" define the format and generation process of a token (though they aren't the only ones; e.g. not all signed tokens are JWTs and not all encrypted tokens are Kerberos tickets).
- "API key", "session token", "refresh token", "OAuth authorization code", "OAuth state value", "access token", and "anti-CSRF token" define the purpose and use of a token.
- Both JWTs and Kerberos tickets are designed so they can be used in situations where the token-issuing server (generally the authenticating server) and the authentication/authorization-using app (the "resource server" in OAuth-ese) are different, and might not even fully trust one another. You can do this (with sometimes-severe performance implications) with other token types though, and the main value of JWTs is in their server-side stateless nature (many JWTs are signed with an HMAC, which requires a symmetric key so the consuming server must be identical to or at least trusted by the issuing server, or else the receiving server must relay the token to the issuing server for validation which is slow).
- OAuth and OIDC are ways to delegate authorization and authentication to another site.
- The other site will still need to authenticate the user, probably initially using something like username+password in an HTML form, and then creating and storing a cookie.
- The site using OAuth will generally still want to create a local session, because OAuth is way too slow to do all the time. Even if the site is actually using the OAuth-returned access token multiple times (rather than at most once for OIDC - or just extracting the user identity from the token directly - and then forgetting about it, the way OAuth-for-SSO typically works), the OAuth-using site will want to create a session so it knows what client is associated with the returned access token (and, if relevant, refresh token).
- The site using OAuth will typically use a cookie or similar even before authentication, to store the
state parameter that is used for OAuth. This typically-random, must-be-unpredictable string is used to prevent CSRF attacks against the resource server, where an attacker could pass their own authentication server response through your browser and make the resource server think your account and the attacker's account are the same.
- OAuth/OIDC isn't even the only protocol(s) used for delegating auth on the web, much less in general. SAML (Security Assertion Markup Language) is widely used in enterprise-scale SSO, and while Kerberos is relatively rarely used for web properties in most environments, it's very common in Windows Domain-based (and sometimes seen in other Kerberos-based) corporate networks and has built-in support in some browsers.
- OAuth/OIDC typically only handle the initial authentication/authorization flow. One problem here is that if the user's access changes at the authentication server - e.g. they are promoted to an Administrator, or their account is locked - sites that used OAuth/OIDC won't necessarily see the change (until the next time the require the user to authenticate, typically because a session expired or was ended but sometimes for other reasons). There are other protocols that can be used to provide updated user status to third parties, though many sites that use OAuth/OIDC don't yet support them.
Why wouldn't they? Remember, cookies aren't a method of authentication, they are a place to store a method of authentication. The stored value - the token - can be any format, from any source: locally generated random value, locally-minted JWT, remote JWT, or many other things.
In practice, nearly all sites - even SPAs, which are just a fancy way of saying "web app that breaks security scanners, can't be used without JS, and complicates bookmarking/session restore" - are loading resources primarily or exclusively from the same site (in the cookie sense, which is much broader than same origin) that the site is hosted on. For example, Gmail is a highly complex webapp, which in some cases functions as an SPA, but everything it loads is either coming from its own domain (mail.google.com, at least in the USA) or from other Google subdomains (*.google.com), all of which can share cookies just fine. Why introduce the complexity and user-experience-degradation of OAuth - which is designed for accessing third-party content - when everything is first-party?
Are there any security features that make cookies more secure than tokens?
You're still conflating cookies (a place to put things) and tokens (I assume you mean JWTs but in general, a category of thing). Many cookies on many sites use JWTs as their value! Many opaque session tokens (and other non-JWT token types) get stored in places that aren't cookies!
This question is like asking "are there any security features that make roads more secure than trucks?" The comparison doesn't make sense; these aren't the same category of object. Trucks often go on roads (and sometimes go elsewhere). Roads often contain non-truck things, which also might go elsewhere. The security features of roads (gates, barriers, patrol officers, etc.) are completely disjoint from the security features of trucks (door locks, alarms, immobilizers).
Bringing this back to web apps and authentication, the security features of cookies (Secure, HttpOnly, SameSite, Domain, etc.) are completely disjoint from the security features of tokens (bits of entropy, RNG/cipher/signing algorithms used, cryptographic key protections if relevant, scopes, resistance to timing attacks or other side channels, resistance to database compromises, etc.).
If you want to compare random opaque session tokens to JWTs, that's a meaningful comparison (note that both can go in a cookie or elsewhere):
- Random tokens can be revoked easily, JWTs can't.
- Forging random tokens requires write access to the DB, forging JWTs only requires learning a signing key.
- Random tokens might be exposed in a read-only DB breach (hashing prevents this) whereas JWTs won't (though their refresh tokens - which are usually just random tokens - will if not hashed).
- Random tokens require a lookup to verify, which can cause problems if e.g. your DB is overloaded; JWTs usually require either an HMAC (fast) or a asymmetric digital signature (slow) verification, the latter of which can be a problem if the app server is overloaded.
- Random tokens can only be verified by their issuer; JWTs (if using asymmetric signatures) can be verified by anybody.
- Random tokens require storing and synchronizing state, which can lead to errors due to cache coherency failures or DoS due to over-use of synchronization locks; JWTs require only synchronizing the signing/verification keys, which don't change often, and no other caching.
So, there's advantages and tradeoffs both ways. As a practical matter, JWTs are used whenever the "third parties can verify" thing is important, and opaque tokens are used in many but not all cases where it isn't. The inability to revoke JWTs means they need to have very short lifetimes for security reasons (so that if one is compromised, it becomes invalid quickly anyhow), so JWTs are typically paired with refresh tokens... which are usually random opaque tokens anyhow, so there's a sense in which JWTs get the downsides of both worlds.
Alternatively, if you want to compare cookies vs. headers (such as
Authorization: Bearer) as a place to put tokens, that is also a meaningful comparison (though a very different one):
- Cookies create CSRF risk; Bearer tokens are immune.
- Cookies can be "HttpOnly", whereas Bearer tokens are always visible to any malicious script on your site.
- Cookies can sometimes be planted via header injection attacks; Bearer tokens generally can't.
- Cookies can be sent in "simple" CORS requests, whereas Bearer tokens will always require a pre-flight for cross-origin requests so it's slightly harder to mess up your CORS configuration dangerously.
- Cookies can automatically expire after a set time; Bearer tokens have no such feature (although the token value might be self-expiring such that it becomes useless after some point).
- Cookies can be used even with scripts severely restricted or disabled (which reduces attack surface for browsers), where as Bearer tokens require script execution on every authenticated request.
- Cookies might accidentally be sent to placed you didn't intend (such as a subdomain owned by a third party) whereas Bearer tokens only ever go exactly where you choose to send them.
Bearer tokens, by comparison, are only viable for client-script-driven apps (which, in this day of React and GraphQL and so forth, is a lot of the new apps... but there's still tons of webapps out there that do full page generation on the server in response to navigation requests and would need to be totally re-written to be client-script-driven). They do care about same-origin policy, which is good on some ways (avoids the risk of exposing a value to the wrong origin) but encouraging developers to split front-end and back-end origins is risky (if forced to configure CORS at all, people often configure it wrong, making their site much more vulnerable).
OpenID connect allows you to use REST APIs, separate authentication and authorization servers, delegate authentication to external sites... etc. Why do big sites only use it for login with social networks and not the rest of the protocol?
Why should they bother?
- REST APIs: Don't require OAuth/OIDC, you can do it with local authentication and authorization. Also you can do it entirely on the back-end rather than having the client make the API calls, which is sometimes simpler but not quite as scalable.
- Separate authentication and authorization [from resource] servers: you can do that without OAuth/OIDC too, so long as they're all on the same domain suffix (and they almost always are) or you're willing to relay on the back-end. Also the benefits of this aren't super clear (except specifically in SSO situations); your auth DB is usually the same as your resource DB, and is often the bottleneck, so having multiple microservices access it doesn't gain you anything.
- Delegate authentication to external sites: This is literally the one and only thing OAuth/OIDC is for that isn't trivial on the web without it, and most sites don't want it (or at least, only want it as an option, not a mandatory behavior). For example, Facebook has their own auth system, and wants you to use it. Users also dislike it sometimes, because it's a privacy issue; if I sign into example.com with my Apple ID, now Apple knows I use example.com, and maybe I'd rather they didn't. Besides, you can do this without OAuth/OIDC; you just end up either asking the user to enter their credentials for site X into site Y (bad), mostly re-inventing OAuth (also bad; it's over-engineered for a lot of use cases but if you try to invent it from scratch you'll probably miss something important), or making the user futz with API keys (bad because any additional user-action roadblock to using your site is bad, and also even with very simple and clear instructions people will screw it up).
Wow, that turned into a long answer, but I hope it cleared some stuff up for you. Questions or comments appreciated.