Federated identity
In a federated identity system, a website delegates authentication to a third party.
- The third party, which is commonly called an Identity Provider (IdP), manages a user's credentials and can authenticate users.
- The website, which is commonly called a Relying Party (RP), trusts the IdP to make assertions about a user's identity.
When the user wants to sign into the website, the website redirects them to the IdP. The user authenticates to the IdP, and the IdP returns a token to the website indicating that the user authenticated successfully. The website checks that the token is valid, and if it is, signs the user in.
Note: Federated identity is not really an authentication method: it's more like an architecture within which various different authentication methods could be used. That is, an IdP could choose to authenticate users with one or more methods, such as traditional passwords, one-time passwords, biometrics, or passkeys.
This model has some benefits for both users and websites:
- Websites don't have to implement their own authentication, or securely handle user credentials.
- A single IdP can authenticate users for many different websites. This means that the user doesn't have to use a different credential for each site: if credentials are passwords, this reduces the risk of password reuse or the user choosing weak, easy-to-remember passwords.
- If a user already has an account with an IdP that your website trusts to authenticate users, then it's much easier for users to sign up to your site, because they don't need to get new credentials specifically for your site.
In this guide we'll explore how a website can work with an IdP to add federated sign-in for their users. We'll cover:
- The main flows defined in the OpenID Connect (OIDC) protocol, which is the dominant standard for federated identity, and good practices to follow when implementing them.
- How browser restrictions on third-party cookies create problems for federated identity implementations.
- The Federated Credential Management (FedCM) API, which makes the browser's role much more active, to simplify the role of the RP and avoid relying on third-party cookies.
- How a website can choose an IdP to work with, and how this choice can affect the process of implementing federated sign-in.
OpenID Connect
The standard most commonly used for federated identity on the web is OpenID Connect (OIDC), which is an authentication protocol built on top of the OAuth 2.0 authorization framework.
Authentication flow
In this section we'll start by walking through the main authentication flow defined in OIDC. There are many options within the OIDC authentication flow: in this walkthrough we'll present the recommended options, and we will talk later about alternatives.
This flow is defined in the OpenID Connect Core specification.
As a prerequisite, the RP needs to be known to the IdP:
- The IdP needs to have an identifier for the RP, which is called a client ID
- The RP must be able to authenticate itself to the IdP
Authentication could use a shared secret called a client secret or some other mechanism such as TLS client authentication.
Note: The OpenID specifications use the term "OpenID Provider" (OP) to refer to what we call an IdP in this guide.
The first thing to note here is that the flow consists of two parts.
- Authentication request: the RP makes a request to the IdP's authorization endpoint, asking the IdP to authenticate the user. The IdP authenticates the user and returns an authorization code to the RP. The code expires after a short period of time (recommended to be no more than 10 minutes).
- Token request: the RP sends the authorization code to a separate endpoint in the IdP, called the token endpoint, and this endpoint responds with an object containing two tokens:
- An access token, which enables the user to access specific resources in the website (like an API key)
- An ID token, which identifies the user, and enables the RP to sign the user in.
In the authentication request:
-
The user asks to sign into the RP.
-
The RP redirects the browser to the IdP's authorization endpoint, asking it to authenticate the user. The RP can provide various parameters along with the request, including:
client_id: Identifies this RP to the IdP.response_type: Always"code"if we are using the two-part flow described here, which is the recommended option.redirect_uri: The URL in the RP, that the IdP should redirect to once it has attempted to authenticate the user. This is the URL to which the IdP will deliver the authorization code.code_challenge: A cryptographic hash of a secret specific to this authorization request, which will be used by the token endpoint to ensure that the token request is really the counterpart of this authorization request.scope: A list of strings that specify which sets of user data the RP wishes to access.
-
The IdP authenticates the user. The protocol does not specify a particular method for this: the IdP could use a password, a one-time password, a biometric, or any other appropriate method.
-
If authentication is successful, the IdP generates the authorization code. It also stores the
code_challengevalue, and associates it with the authorization code. The IdP then redirects the browser to the RP's redirect URL, passing the authorization code as a parameter.
In the token request:
-
The RP makes a
POSTrequest to the token endpoint. This request includes the following parameters:client_id: Identifies this RP to the IdP.client_secret: The secret used to authenticate the RP to the IdP: this could be any value previously agreed between the RP and the IdP. Instead of a shared secret, the RP and the IdP could use some alternative mechanism for client authentication, such as TLS client authentication.grant_type: This should be"authorization_code".code: The authorization code.code_verifier: This is the original secret that was used to produce thecode_challengeparameter in the authentication request.
-
The IdP validates the request:
- It authenticates the RP using client authentication.
- It hashes the
code_verifierparameter, and then checks that the result matchescode_challenge.
-
If the request is valid, the IdP responds with two tokens:
- An access token, which grants the user access to some resource in the RP.
- An ID token, which identifies the user. This is a cryptographically signed JSON Web Token.
-
The RP validates the tokens: among other checks, it verifies the IdP's signature on the ID token. If validation succeeds, the RP signs the user in.
Security features
In this section we will summarize the main security features of the OIDC authentication flow we've just described. For the full details, see Best Current Practice for OAuth 2.0 Security.
Authorization code flow
The two-step flow we have described is called the "authorization code flow". In an alternative flow, called the "implicit flow", only the first step exists, and the response to the authentication request already contains the access and ID tokens. This is unsafe, because the tokens are exposed to the RP's front end, which is regarded as much less secure than the back end. For example, in a successful XSS attack, or if the user installs a malicious browser extension, then the attacker might be able to steal the user's tokens.
For this reason, websites should always use the authorization code flow. Even if an attacker can steal the authorization code, they still need to convince the token endpoint to give them the tokens in exchange for the code.
Client authentication
In the flow we have described, the RP authenticates itself to the token endpoint when it makes the token request. This means that if an attacker does manage to steal the authorization code, it still has to successfully impersonate the RP to get the tokens from the IdP.
The OAuth specification distinguishes between confidential and public clients. Confidential clients are essentially clients that can keep a secret, and public clients are those that can't.
The specification considers that clients running on the user's browser are public clients, for the same reason we've already encountered: it's too easy for an attacker to access secrets in a browser via attacks such as XSS. Clients running on a web server are confidential clients.
In OIDC, only confidential clients may use client authentication, because only confidential clients can be trusted to maintain the security of the client's credentials.
The RP can authenticate itself to the IdP using a shared secret, but it is better to use a method based on public-key cryptography, such as TLS client authentication.
Proof Key for Code Exchange (PKCE)
The code_challenge and code_verifier values that the RP provides in the authentication request and token request, respectively, are part of a mechanism called Proof Key for Code Exchange (PKCE), specified in RFC 7636.
In the authentication request:
- The RP generates a value that is hard to guess and is specific to this authentication request. This value is called the code verifier.
- The RP creates a cryptographic hash of the code verifier, and uses it as the
code_challengeparameter in the authentication request. - The IdP stores the code challenge, and associates it with the authorization code that it returns to the RP.
In the token request:
- The RP passes the code verifier in the code_verifier parameter.
- The IdP hashes the code verifier, and compares the result with the stored code challenge: if they do not match, then the token request is denied.
This defends against two attacks: CSRF against the RP's redirect URL, and authorization code injection.
CSRF against the redirect URL
In a CSRF attack, the attacker tricks the user's browser into signing the user into the attacker's account. This can have various bad effects: for example, any private data the user uploads to the account is available to, and under control of, the attacker.
If PKCE were not used, the CSRF attack works as follows:
-
The attacker asks to sign into the RP. The RP makes an authentication request to the IdP and the attacker authenticates to the IdP.
-
The IdP generates an authorization code for the attacker, and redirects the attacker's browser to the RP's redirect URL, with the authorization code as a URL parameter.
-
The attacker intercepts this redirect, extracts the redirect URL including the authorization code, and terminates the flow.
-
The attacker tricks the user into clicking the redirect URL. To the RP, this looks like a response from the IdP to an authentication request originating from the user.
-
The RP makes a token request to the IdP, including the attacker's authorization code, which it took from the redirect URL.
-
The IdP responds with the attacker's tokens.
-
The RP signs the user into the attacker's account: now any information or instructions they provide are under the attacker's control.
Essentially, the attack succeeds because the RP doesn't know that the request to the redirect URL is not a response to a request made on behalf of the user.
When PKCE is used:
- In step 1, the RP generates a code verifier for the attacker's request, and sends the hashed code verifier (the code challenge) to the IdP.
- In step 2, the IdP stores the code challenge alongside the attacker's authorization code.
- In step 5, the RP won't be able to find a code verifier for the user that matches the challenge the IdP stored, so the token request will fail.
An alternative defense is the state parameter defined in OAuth 2.0. In this defense, the RP provides an unpredictable value as a parameter in the authentication request, and the IdP includes the same value in the response: the RP checks that they match. Because the attacker can't predict the value of state, they can't pass a matching value to the RP's redirect URL.
Authorization code injection
In an authorization code injection attack, the attacker steals an authorization code from the target user, and is able to inject it into the attacker's own sign-in flow. The result is that the attacker is signed into the user's account.
It's generally accepted that authorization codes in OIDC are vulnerable, in part because they're exposed to the user's browser. For example, if the user installs a malicious browser extension, then it will be able to steal authorization codes.
The main mitigation here is client authentication: because the RP authenticates itself to the IdP when it makes a token request, an attacker can't just make their own token request with the stolen code. However, with the authorization code injection attack, it's the real RP making the token request, so client authentication is successful.
If PKCE were not used, the authorization code injection attack works as follows:
-
The attacker is able to steal the user's authorization code. For example, the user has installed a malicious browser extension that can access the URLs that the browser visits.
-
The user tries to sign in. The RP makes an authentication request, the user authenticates, and the IdP redirects the browser to the RP's redirect URL, with the authorization code as a URL parameter.
-
At this point, the malicious browser extension retrieves the authorization code, sends it to the attacker, and terminates the user's authentication flow.
-
The attacker receives the user's authorization code.
-
The attacker starts its own OIDC authentication flow, but intercepts the IdP's authentication response, replacing the authorization code with the code it stole from the user. This is straightforward, because the authentication response is for the attacker, so it passes through the attacker's device.
-
The RP then continues its authentication flow for the attacker by making the token request to the IdP, including the user's authorization code that the attacker has injected.
-
The IdP responds with the user's tokens.
-
The RP signs the attacker into the user's account.
Note that the state parameter doesn't help here, because the authentication request and response really do belong to the same flow - the attacker's.
PKCE protects against this attack, because:
- In step 2, the RP generates a code verifier and sent the hashed code challenge to the IdP, which stores the challenge alongside the user's code.
- In step 6, the RP's token request contains the attacker's code verifier but the user's code. The IdP looks up the code challenge for the user's code: it won't match the attacker's code verifier, and the token request will be denied.
An alternative to PKCE, specified in OIDC, is the nonce value. The RP includes this as another parameter in the authentication request: the IdP stores it, and the token endpoint returns it to the RP along with the tokens. The RP then checks that the returned value is the same as the original value.
Ensuring that PKCE is used
Although PKCE provides effective protection against the attacks described here, it's an optional part of the protocol. This means that an RP must ensure that its chosen IdP not only supports PKCE, but that it mandates the use of PKCE, refusing the token request if a valid code verifier is not included.
Otherwise, an RP is vulnerable to a PKCE downgrade attack, in which an attacker tricks the IdP into thinking that the RP does not wish to use PKCE in a token request.
Architectures for OIDC clients
The OAuth 2.0 for Browser-Based Applications specification describes how web application architecture can affect the security threats faced by OIDC clients (that is, relying parties), and makes some recommendations for web application architecture.
In particular, it finds that:
-
The most secure pattern is one in which the website uses a web server to handle all OAuth/OIDC interactions and interactions with APIs that are protected by access tokens. In this pattern, the RP can be a confidential client, because it can keep client secrets in the server. It can also keep all tokens in the server, including access tokens.
-
The next most secure pattern is one in which the website uses a web server to handle all OAuth/OIDC interactions, but then returns the access token to the front end, and the front end then makes API requests directly. In this scenario the website can be a confidential client but malicious code running in the browser (for example, through an XSS attack) can potentially steal access tokens. However, the front end doesn't have to store access tokens long-term: it can retrieve them from the backend when it needs them.
-
The least secure pattern is one in which OAuth/OIDC interactions and interactions with APIs both take place in the front end. This, for example, would be the natural architecture for a Single-page app, where the entire application executes in the browser. In this architecture the RP can't be a confidential client, because it can't reliably keep a client secret. This means that it can't authenticate itself to the IdP. It also has to persistently store tokens, which increases the risk of malicious code stealing them.
The specification also includes detailed recommendations for security practices to follow in each of these three scenarios.
OIDC sign-out
Sign-out scenarios are more complex in a federated identity system than in a non-federated system, because:
- The user might sign out of the RP either on the RP's site or on the IdP's site.
- The user might choose to sign out of the RP only, or to sign out globally: that is, to sign out of all RPs that they are signed into with this IdP. This is a common requirement when we use federated identity to build a single sign-on (SSO) system, in which an employee might use a single set of corporate credentials to sign into email, a bug tracker, and a discussion forum.
Supporting these scenarios means implementing some communication mechanism between the RP and the IdP. For example:
- If the user signs out at the IdP, then the RP should be notified, and sign the user out in the RP.
- If the user signs out at the RP, then the IdP should be notified, and be able to sign the user out of all RPs that they are currently signed into.
The OpenID specifications define two general approaches to implementing this coordination, which they call "front channel logout" and "back channel logout".
In front channel logout, the browser is used to mediate the communication. In this approach, the sender's page embeds an <iframe> whose content is loaded from the recipient. For example, if the user signs out at the IdP, the IdP may embed an <iframe> whose src attribute points to the RP's logout URL: when the <iframe> is rendered, the browser makes a GET request to that URL, which the RP interprets as an instruction to sign the user out.
In back channel logout, the RP and the IdP communicate directly with each other, bypassing the browser. For example, when the IdP needs to tell the RP to sign the user out, the IdP makes a POST request directly to the RP.
Third-party cookies
When implementing a federated identity system, we have to coordinate the interactions between the RP, the IdP, and the user. Some implementations of this coordination depend on browser support for third-party cookies.
For example, in front-channel logout (one of the approaches to implementing sign-out in OpenID Connect) we use cross-site <iframe> elements, in which the RP's document contains an <iframe> whose content is loaded from the IdP, or vice versa. This depends on the embedded <iframe> being able to send its cookies to its origin.
Similarly, while the main OpenID Connect authentication flow uses full-page redirects to coordinate between the participants, this is a distracting experience for users and is difficult for single-page apps to support. A better user experience can be achieved by embedding the IdP as an <iframe> in the RP's page, and this again depends on third-party cookies.
However, because third-party cookies are widely used for tracking users, browsers have taken steps to deprecate and remove support for them, and they are now not supported by default in some browsers.
As such, we recommend not implementing federated identity features in a way that depends on third-party cookies.
The FedCM API
The Federated Credential Management API (FedCM API) provides built-in browser support for federated identity. The API does not yet have cross-browser support and is still being actively developed, so we can't fully recommend its use, but it promises several benefits over implementing a protocol like OpenID Connect directly:
- In the OIDC flow we've previously described, the website using OIDC (that is, the RP) has to coordinate the interactions between itself, the user, and the IdP. As we've seen, this is complicated and error-prone. With FedCM, the browser takes care of this interaction: as an RP, you call a browser API, and the browser locates the IdP, asks the user to authenticate, and returns a token from the IdP that the RP can use to sign the user in.
- As a consequence of this, you don't have to rely on third-party cookies, so FedCM will work on browsers that block them.
- In FedCM, the interface in which the user authenticates to the IdP is built into the browser, offering them a more consistent and seamless experience without redirects.
FedCM is integrated into the Credential Management API, which is a framework that enables browsers to work with a variety of different sorts of credentials. To authenticate using the FedCM API, you call CredentialsContainer.get(), passing in the various options including:
- Identifiers for the IdP(s) that the user may use to sign into this RP
- The context in which the RP is using the IdP (for example, whether the user is registering or signing in).
When you call CredentialsContainer.get(), the browser will:
- Contact the IdPs that you have specified
- Ask the user to sign into their chosen IdP, if they are not already signed in
- Ask the IdP to verify the user's identity
- Return a token which the RP can use to sign the user in.
FedCM and federated identity protocols
FedCM does not itself implement a federated identity protocol such as OIDC. You can think of it as providing transport between the RP, the user, and the IdP, but it is agnostic about the items that are exchanged or their interpretation.
For example, in an implementation of OIDC using FedCM, the token returned by CredentialsContainer.get() may be an authorization code, and the RP will then have to retrieve the identity token from the IdP's token endpoint. That is, FedCM takes care of only the first part of the authentication flow. The FedCM for OAuth document describes how OAuth and OIDC could be implemented using FedCM.
In general, when an RP decides to use a particular IdP for federated login, the RP will register with the IdP, and as part of this process the IdP should explain to the RP exactly which arguments it expected to be given, how it should handle the objects that the IdP returns, and any other behavior it expects the RP to implement.
Choosing IdPs
When you decide to add federated sign-in to your site, one of the fundamental choices you'll face is choosing which identity providers to work with. If a potential user of your site already has an account with one of your chosen IdPs, it's much easier for them to create a new account on your site.
So you're likely to see more sign-ups if a large proportion of your expected userbase already has an account with your chosen IdP.
It's common for a website to enable its users to sign in with more than one IdP, to cover more users and give them more choice. However, offering too many options leads to a confusing user experience, and users who have accounts at more than one of your IdPs may have trouble remembering which one they signed up with.
It's also common practice to provide a fallback option for users who can't use any of your chosen IdPs. In this option, users authenticate directly with your site using a method such as a password or an OTP.
Whichever identity providers you choose will provide detailed instructions and tools for integrating your site with their system, and this is likely to take care of many of the complexities inherent in a protocol like OIDC. However, it's still extremely helpful to understands what's happening under the surface.
Strengths and weaknesses
For web developers, the biggest benefit of using federated identity is reducing sign-up friction for those users who already have an account with one of the chosen IdPs. Additionally, their chosen IdPs can help websites to securely implement federated identity.
From a security perspective, the biggest benefit is that because users don't have to create new credentials for each account, there's a lower risk of them choosing passwords that are easy to remember (that is, weak) or of them reusing passwords across sites.
We can say that federated identity is a more secure option than just passwords, but it still has problems:
-
The advantages to websites of choosing IdPs which have a large userbase means that the space tends to be monopolized by a few very large providers. This in turn tends to lock users into those providers, leading websites to offer a worse experience for users who don't want to (or can't) use them.
-
Unless a website is willing to completely lock out users who don't want to (or can't) sign up with their chosen IdPs, then the site still has to deal with all the complexity of implementing a fallback authentication method.
-
Like any authentication system that relies on the user entering a secret into a website, federated identity is vulnerable to phishing attacks.