diff --git a/Documentation/proposals/upstream-refreshing.md b/Documentation/proposals/upstream-refreshing.md
new file mode 100644
index 0000000000000000000000000000000000000000..019bd9f890840871ff5c5163052f0afc766817ac
--- /dev/null
+++ b/Documentation/proposals/upstream-refreshing.md
@@ -0,0 +1,165 @@
+# Proposal: upstream refreshing
+
+## TL;DR
+
+Today, if a user deletes their GitHub account, dex will keep allowing clients to
+refresh tokens on that user's behalf because dex never checks back in with
+GitHub.
+
+This is a proposal to change the connector package so the dex can check back
+in with GitHub.
+
+## The problem
+
+When dex is federaing to an upstream identity provider (IDP), we want to ensure
+claims being passed onto clients remain fresh. This includes data such as Google
+accounts display names, LDAP group membership, account deactivations. Changes to
+these on an upstream IDP should always be reflected in the claims dex passes to
+its own clients.
+
+Refresh tokens make this complicated. When refreshing a token, unlike normal
+logins, dex doesn't have the opportunity to prompt for user interaction. For
+example, if dex is proxying to a LDAP server, it won't have the user's username
+and passwords.
+
+Dex can't do this today because connectors have no concept of checking back in
+with an upstream provider (with the sole exception of groups). They're only 
+called during the initial login, and never consulted when dex needs to mint a
+new refresh token for a client. Additionally, connectors aren't actually aware
+of the scopes being requested by the client, so they don't know when they should
+setup the ability to check back in and have to treat every request identically.
+
+## Changes to the connector package
+
+The biggest changes proposed impact the connector package and connector
+implementations.
+
+1. Connectors should be consulted when dex attempts to refresh a token.
+2. Connectors should be aware of the scopes requested by the client.
+
+The second bullet is important because of the first. If a client isn't
+requesting a refresh token, the connector shouldn't do the extra work, such as
+requesting additional upstream scopes.
+
+to address the first point, a top level `Scopes` object will be added to the
+connector package to express the scopes requested by the client. The
+`CallbackConnector` and `PasswordConnector` will be updated accordingly.
+
+```go
+// Scopes represents additional data requested by the clients about the end user.
+type Scopes struct{
+	// The client has requested a refresh token from the server.
+	OfflineAccess bool
+
+	// The client has requested group information about the end user.
+	Groups bool
+}
+
+// CallbackConnector is an interface implemented by connectors which use an OAuth
+// style redirect flow to determine user information.
+type CallbackConnector interface {
+	// The initial URL to redirect the user to.
+	//
+	// OAuth2 implementations should request different scopes from the upstream
+	// identity provider based on the scopes requested by the downstream client.
+	// For example, if the downstream client requests a refresh token from the
+	// server, the connector should also request a token from the provider.
+	LoginURL(s Scopes, callbackURL, state string) (string, error)
+
+	// Handle the callback to the server and return an identity.
+	HandleCallback(s Scopes, r *http.Request) (identity Identity, state string, err error)
+}
+
+// PasswordConnector is an interface implemented by connectors which take a
+// username and password.
+type PasswordConnector interface {
+	Login(s Scopes, username, password string) (identity Identity, validPassword bool, err error)
+}
+```
+
+The existing `GroupsConnector` plays two roles.
+
+1. The connector only attempts to grab groups when the downstream client requests it.
+2. Allow group information to be refreshed.
+
+The first issue is remedied by the added `Scopes` struct. This proposal also
+hopes to generalize the need of the second role by adding a more general
+`RefreshConnector`:
+
+```go
+type Identity struct {
+	// Existing fields...
+
+	// Groups are added to the identity object, since connectors are now told
+	// if they're being requested.
+
+	// The set of groups a user is a member of.
+	Groups []string
+}
+
+
+// RefreshConnector is a connector that can update the client claims.
+type RefreshConnector interface {
+	// Refresh is called when a client attempts to claim a refresh token. The
+	// connector should attempt to update the identity object to reflect any
+	// changes since the token was last refreshed.
+	Refresh(s Scopes, identity Identity) (Identity, error)
+
+	// TODO(ericchiang): Should we allow connectors to indicate that the user has
+	// been delete	or an upstream token has been revoked? This would allow us to
+	// know when we should remove the downstream refresh token, and when there was
+	// just a server error, but might be hard to determine for certain protocols.
+	// Might be safer to always delete the downstream token if the Refresh()
+	// method returns an error.
+}
+```
+
+## Example changes to the "passwordDB" connector
+
+The `passwordDB` connector is the internal connector maintained by the server. 
+As an example, these are the changes to that connector if this change was
+accepted.
+
+```go
+func (db passwordDB) Login(s connector.Scopes, username, password string) (connector.Identity, bool, error) {
+	// No change to existing implementation. Scopes can be ignored since we'll
+	// always have access to the password objects.
+
+}
+
+func (db passwordDB) Refresh(s connector.Scopes, identity connector.Identity) (connector.Identity, error) {
+	// If the user has been deleted, the refresh token will be rejected.
+	p, err := db.s.GetPassword(identity.Email)
+	if err != nil {
+		if err == storage.ErrNotFound {
+			return connector.Identity{}, errors.New("user not found")
+		}
+		return connector.Identity{}, fmt.Errorf("get password: %v", err)
+	}
+
+	// User removed but a new user with the same email exists.
+	if p.UserID != identity.UserID {
+		return connector.Identity{}, errors.New("user not found")
+	}
+
+	// If a user has updated their username, that will be reflected in the
+	// refreshed token.
+	identity.Username = p.Username
+	return identity, nil
+}
+```
+
+## Caveats
+
+Certain providers, such as Google, will only grant a single refresh token for each
+client + end user pair. The second time one's requested, no refresh token is
+returned. This means refresh tokens must be stored by dex as objects on an
+upstream identity rather than part of a downstream refresh even.
+
+Right now `ConnectorData` is too general for this since it is only stored with a
+refresh token and can't be shared between sessions. This should be rethought in
+combination with the [`user-object.md`](./user-object.md) proposal to see if
+there are reasonable ways for us to do this.
+
+This isn't a problem for providers like GitHub because they return the same
+refresh token every time. We don't need to track a token per client.