docs/auth: added overview, oauth description and glossary
This commit is contained in:
@@ -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.
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
@@ -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.
|
||||
|
||||
<!--
|
||||
@startuml oauth-popup-flow
|
||||
|
||||
skinparam monochrome true
|
||||
skinparam shadowing false
|
||||
skinparam backgroundColor #fefefe
|
||||
skinparam defaultFontName Segoe UI, Helvetica, Arial, sans-serif
|
||||
|
||||
title OAuth Consent and Refresh Flow
|
||||
|
||||
participant Browser
|
||||
participant "Popup Window" as Popup
|
||||
participant "auth-backend plugin" as Backend
|
||||
control "Consent Screen" as Consent
|
||||
entity "OAuth Provider" as Provider
|
||||
|
||||
note over Browser: Components on page ask for an\naccess token with greater\nscope than the existing session.
|
||||
|
||||
Browser -> Popup: Open popup
|
||||
Popup -> Backend: GET /auth/<provider>/start?scope=some%20scopes
|
||||
Popup <- Backend: Redirect to consent screen with\nrandom nonce in OAuth state and\nshort-lived cookie with the same nonce.
|
||||
Popup -> Consent: GET /consent_url?redirect_uri=<redirect_uri>?nonce=<n>\nwhere redirect_uri=<app-origin>/auth/<provider>/handler/frame
|
||||
|
||||
note over Consent: User consents to\naccess the new scope.
|
||||
|
||||
Popup <- Consent: Redirect to given redirect URL, with authorization code
|
||||
Popup -> Backend: GET /auth/<provider>/handler/frame?code=<c>&nonce=<n>\nRequest includes the previously set none cookie
|
||||
|
||||
note over Backend: Verify that the nonce in the cookie\nmatches the nonce in the OAuth state
|
||||
|
||||
Backend -> Provider: Authorization Code\nClient ID\nClient Secret
|
||||
|
||||
note over Provider: Verify and generate tokens
|
||||
|
||||
Backend <- Provider: Access Token\n(ID Token)\n(Refresh Token)\nScope\nExpire Time
|
||||
Popup <- Backend: Small HTML page with inlined response payload\nStore Refresh Token in HTTP-only cookie
|
||||
Browser <- Popup: postMessage() with tokens and info or error
|
||||
Popup -> Popup: Close self
|
||||
|
||||
note over Browser: A later point when a refresh\n is needed. Either because of\n a reload or an expiring session.
|
||||
|
||||
Browser -> Backend: GET /auth/<provider>/token\nRefresh Token cookie included
|
||||
Backend -> Provider: Refresh Token\nClient ID\nClient Secret
|
||||
Backend <- Provider: Access Token\n(ID Token)\nScope\nExpire Time
|
||||
Browser <- Backend: Tokens and info
|
||||
|
||||
@enduml
|
||||
-->
|
||||
|
||||

|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user