From 4f3375073db11872897165356d3e2d96ef259e78 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 9 Jun 2020 18:19:47 +0200 Subject: [PATCH] docs/auth: added overview, oauth description and glossary --- docs/auth/glossary.md | 26 ++++++ docs/auth/oauth-popup-flow.svg | 56 ++++++++++++ docs/auth/oauth.md | 151 +++++++++++++++++++++++++++++++++ docs/auth/overview.md | 44 ++++++++++ 4 files changed, 277 insertions(+) create mode 100644 docs/auth/glossary.md create mode 100644 docs/auth/oauth-popup-flow.svg create mode 100644 docs/auth/oauth.md create mode 100644 docs/auth/overview.md diff --git a/docs/auth/glossary.md b/docs/auth/glossary.md new file mode 100644 index 0000000000..6408587273 --- /dev/null +++ b/docs/auth/glossary.md @@ -0,0 +1,26 @@ +# Glossary + +- **Popup** - A separate browser window opened on top of the previous one. +- **OAuth** - More specifically OAuth 2.0, a standard protocol for + authorization. See [oauth.net/2/](https://oauth.net/2/). +- **OpenID Connect** - A layer on top of OAuth which standardises + authentication. See + [en.wikipedia.org/wiki/OpenID_Connect](https://en.wikipedia.org/wiki/OpenID_Connect). +- **JWT** - JSON Web Token, a popular JSON based token format that is commonly + encrypted and/or signed, see + [en.wikipedia.org/wiki/JSON_Web_Token](https://en.wikipedia.org/wiki/JSON_Web_Token) +- **Scope** - A string that describes a certain type of access that can be + granted to a user using OAuth. +- **Access token** - A token that gives access to perform actions on behalf of a + user. It will commonly have a short expiry time, and be limited to a set of + scopes. Part of the OAuth protocol. +- **ID token** - A JWT used to prove a user's identity, containing for example + the user's email. Part of OpenID Connect. +- **Offline access** - OAuth flow that results in both a refresh and access + token, where the refresh token has a long expiration or never expires, and can + be used to request more access tokens in the future. This lets the user go + "offline" with respect to the token issuer, but still be able to request more + tokens at a later time without further direct interaction for the user. +- **Code grant** - OAuth flow where the client receives an authorization code + that is passed to the backend to be exchanged for an access token and possibly + refresh token. diff --git a/docs/auth/oauth-popup-flow.svg b/docs/auth/oauth-popup-flow.svg new file mode 100644 index 0000000000..4d9e79787a --- /dev/null +++ b/docs/auth/oauth-popup-flow.svg @@ -0,0 +1,56 @@ +OAuth Consent and Refresh FlowBrowserBrowserPopup WindowPopup Windowauth-backend pluginauth-backend pluginConsent ScreenConsent ScreenOAuth ProviderOAuth ProviderComponents on page ask for anaccess token with greaterscope than the existing session.Open popupGET /auth/<provider>/start?scope=some%20scopesRedirect to consent screen withrandom nonce in OAuth state andshort-lived cookie with the same nonce.GET /consent_url?redirect_uri=<redirect_uri>?nonce=<n>where redirect_uri=<app-origin>/auth/<provider>/handler/frameUser consents toaccess the new scope.Redirect to given redirect URL, with authorization codeGET /auth/<provider>/handler/frame?code=<c>&nonce=<n>Request includes the previously set none cookieVerify that the nonce in the cookiematches the nonce in the OAuth stateAuthorization CodeClient IDClient SecretVerify and generate tokensAccess Token(ID Token)(Refresh Token)ScopeExpire TimeSmall HTML page with inlined response payloadStore Refresh Token in HTTP-only cookiepostMessage() with tokens and info or errorClose selfA later point when a refreshis needed. Either because ofa reload or an expiring session.GET /auth/<provider>/tokenRefresh Token cookie includedRefresh TokenClient IDClient SecretAccess Token(ID Token)ScopeExpire TimeTokens and info \ No newline at end of file diff --git a/docs/auth/oauth.md b/docs/auth/oauth.md new file mode 100644 index 0000000000..519cfe1d85 --- /dev/null +++ b/docs/auth/oauth.md @@ -0,0 +1,151 @@ +# OAuth and OpenID Connect + +This section describes how Backstage allows plugins to request OAuth Access +Tokens and OpenID Connect ID Tokens on behalf of the user, to be used for auth +to various third party APIs. + +## Summary + +There are occasions when the user wants to perform actions towards third party +services that require authorization via OAuth. Backstage provides standardized +[Utility APIs](../getting-started/utility-apis.md) such as the +[GoogleAuthApi](../../packages/core-api/src/apis/definitions/auth.ts) for that +use-case. Backstage also includes a set of implementations of these APIs that +integrate with the [auth-backend](../../plugins/auth-backend) plugin to provide +a popup-based OAuth flow. + +## Background + +Access control in OAuth is implemented in terms of scope, which is a list of +permissions given to the app. An OAuth service can issue Access Tokens that are +tied to a certain set of scopes, such as viewing profile information, reading +and/or writing user data in the service. The scope format and handling is +specific to each OAuth provider, and the set of available scopes are typically +found in the documentation describing the auth solution of the provider, for +example +[developers.google.com/identity/protocols/oauth2/scopes](https://developers.google.com/identity/protocols/oauth2/scopes). + +As a part of logging in with an OAuth provider, the user needs to consent to +both the login itself and the set of scopes that the app is requesting to use. +This is done by loading a page provided by the OAuth provider, where a user can +choose an account to log in with, and accept or reject the request. If the user +accepts the login request, a token is issued, and any holder of the token can +use it to make authenticated requests towards the third party service. + +## OAuth in @backstage/core-api and auth-backend + +The default OAuth implementation in Backstage is based on an OAuth server-side +offline access flow, which means that it uses the backend as a helper in order +to trade credentials. A benefit of this type of flow is that it does not require +the use of third party cookies, and is robust on a wide selection of browsers +and privacy browsing plugins, strict security settings, etc. + +The implementation also uses a popup-based flow, where auth requests are handled +in a new popup window that is opened by the app. By using a popup-based flow it +is possible to request authentication at any point in the app, without requiring +a redirect. Because of this there is no need to ask for all scopes upfront, or +interrupt the app with a redirect and forcing plugin authors to take care in +restoring state after a redirect has been make. All in all it makes it much +easier to make authenticated requests inside a plugin. + +## OAuth Flow + +The following describes the OAuth flow implemented by the +[auth-backend](../../plugins/auth-backend) and +[DefaultAuthConnector](../../packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts) +in `@backstage/core-api`. + +Component and APIs can request Access or ID Tokens from any available Auth +provider. If there already exists a cached fresh token that covers (at least) +the requested scopes, it will be returned immediately. If the OAuth provider +implements token refreshes, this check will also trigger a token refresh attempt +if no session is a available. + +If new scopes are requested, or the user is not yet logged in with that +provider, a dialog is shown informing the user that they need to log in with the +specified provider. If the user agrees to continue, a separate popup window is +opened that implements the entire consent flow. + +The popup window is pointed to the `/start` endpoint of the auth provider in the +`auth-backend` plugin, which then redirects to the OAuth consent screen of the +provider. The consent screen is controlled by the OAuth provider, and will do +things like prompting the user to log in with an account, and possibly reviewing +the set of requested scopes. If the login request is accepted, the popup window +will be redirected back to the `/handler/frame` endpoint of the auth backend. +The redirect URL will contain a short-term authorization code, which is picked +up by the backend and exchanged for long-term tokens via a call to the OAuth +provider. The Access and possibly ID Token is then handed back to the main +Backstage page via `postMessage`. If the OAuth provider implements offline +refresh, a refresh token will be stored in an HTTP-only cookie scoped to the +specific provider in the `auth-backend` plugin. + +To protect against certain attacks, the above flow also includes a simple nonce +check and a lightweight CSRF protection header. The nonce check is done to +protect against attacks where an attacker tricks a user to log in with an +account of the attacker's choosing in order to gather data. In the first part of +the flow where the popup is directed to the `/start` endpoint, a nonce is +generated and placed in both a cookie and the OAuth state. The nonces received +in the cookie and OAuth state in the redirect handler are then checked, and the +auth attempt will fail if they're not valid. The CSRF protection for the +`/refresh` and `/logout` endpoints is implemented by simply checking for the +presence of a `X-Requested-With` header. + +The target origin of the `postMessage` is also of importance to keep the flow +secure. It is configured to a single value for each auth provider and +environment. Without a single configured origin, any page could open a popup and +request an access token. + +### Sequence Diagram + +The following diagram visualizes the flow described in the previous section. + + + +![](oauth-popup-flow.svg) diff --git a/docs/auth/overview.md b/docs/auth/overview.md new file mode 100644 index 0000000000..2806b2dccf --- /dev/null +++ b/docs/auth/overview.md @@ -0,0 +1,44 @@ +# User Authentication and Authorization in Backstage + +## Summary + +The purpose of the Auth APIs in Backstage are to identify the user, and to +provide a way for plugins to request access to 3rd party services on behalf of +the user (OAuth). This documentation focuses on the implementation of that +solution and how to extend it. For documentation on how to consume the Auth APIs +in a plugin, see [TODO](#TODO). + +### Accessing Third Party Services + +The main pattern for talking to third party services in Backstage is +user-to-server requests, where short-lived OAuth Access Tokens are requested by +plugins to authenticate calls to external services. These calls can be made +either directly to the services or through a backend plugin or service. + +By relying on user-to-server calls we keep the coupling between the frontend and +backend low, and provide a much lower barrier for plugins to make use of third +party services. This is in comparison to for example a session-based system, +where access tokens are stored server-side. Such a solution would require a much +deeper coupling between the auth backend plugin, its session storage, and other +backend plugins or separate services. A goal of Backstage is to make it as easy +as possible to create new plugins, and an auth solution based on user-to-server +OAuth helps in that regard. + +The method with which frontend plugins request access to third party services is +through [Utility APIs](../getting-started/utility-apis.md) for each service +provider. For a full list of providers, see [TODO](#TODO). + +### Identity - TODO + +This documentation currently only covers the OAuth use-case, as identity +management is not settled yet and part of an +[upcoming milestone](https://github.com/spotify/backstage/milestone/12). + +## Further Reading + +More details are provided in dedicated sections of the documentation. + +- [OAuth](./oauth): Description of the generic OAuth flow implemented by the + [auth-backend](../../plugins/auth-backend). +- [Glossary](./glossary): Glossary of some common terms related to the auth + flows.