---
tags: api-design
---
# Pinniped Supervisor `response_mode=form_post` Support
## Introduction
This document describes a design to solve [pinniped/#668](https://github.com/vmware-tanzu/pinniped/issues/668) (_Support for OIDC logins on hosts without a local web browser ("jump host")_) by adding support for `response_mode=form_post` in the Pinniped Supervisor.
### What is `response_mode=form_post`?
The `response_mode` parameter is defined in [OAuth 2.0 Multiple Response Type Encoding Practices](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html). It is a counterpart to `response_type` which "informs the Authorization Server of the mechanism to be used for returning Authorization Response parameters from the Authorization Endpoint". The current behavior in Pinniped corresponds to `response_mode=query`, where the response is returned via an HTTP redirect with URL query parameters.
The meaning of `response_mode=form_post` is defined by [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). Instead of an HTTP redirect, the response parameters such as authorization code, state, and granted scopes are "encoded as HTML form values that are auto-submitted in the User Agent, and thus are transmitted via the HTTP POST method to the Client".
There is also a well known field in the OIDC discovery document to advertise supported response modes (`response_modes_supported` defined in [OpenID Connect Discovery 1.0 incorporating errata set 1](https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3)).
### How does this work in Fosite?
Fosite supports this functionality since November 2020 ([fosite/#470](https://github.com/ory/fosite/issues/470)). The [default HTML template](https://github.com/ory/fosite/blob/0a48821b156f4a5dffa0f7149d30d5cf02636f37/authorize_helper.go#L37-L50) shows a minimal zero-interaction example of the response:
```go
var FormPostDefaultTemplate = template.Must(template.New("form_post").Parse(`<html>
<head>
<title>Submit This Form</title>
</head>
<body onload="javascript:document.forms[0].submit()">
<form method="post" action="{{ .RedirURL }}">
{{ range $key,$value := .Parameters }}
{{ range $parameter:= $value}}
<input type="hidden" name="{{$key}}" value="{{$parameter}}"/>
{{end}}
{{ end }}
</form>
</body>
</html>`))
```
We can override this to provide our own HTML and JavaScript. We need to do a little bit of setup to tell Fosite that our `pinniped-cli` client is allowed to use this response type and that our authorization server is prepared to handle it.
## Tweaking `form_post` to support the "jump host" case
The key observation of this design is that the HTML page in the `response_mode=form_post` response is also well-positioned to have extended behavior that allows for a choice of two login flows, selected dynamically by JavaScript:
1. *Successfully send* a POST request to the (localhost) callback endpoint then show a "success" message.
2. *Fail to send* a POST request to the (localhost) callback, then show a "please copy the auth code" message allowing the user to manually complete the login by copying the response parameters from the Supervisor-hosted page and pasting into the waiting CLI process.
Note that this server behavior is still 100% compatible with standard OIDC clients in the first case. The second case extends beyond the spec but follows other known examples (e.g., the Concourse `fly` login flow).
There are also corresponding changes to the `pinniped login oidc` command:
- If the issuer advertises support for the `form_post` response mode (in OIDC discovery), then add `response_mode=form_post` as a extra parameter on the authorization request.
- If `form_post` is selected:
- Expect the callback to receive an HTTP POST instead of a GET, with parameters encoded in the request body rather than the URL query string.
- If stdin is a TTY, print out a prompt to paste the authorization code. Spawn a goroutine that runs concurrently with the callback handler. This goroutine should accept a pasted authorization code and complete the login flow.
## Security
The authorization code will now be copy-pasted by the user, which exposes it to being leaked by inadvertantly pasting it in the wrong window (e.g., Slack). However, this is well mitigated by several existing mechanisms:
- Authorization codes expire quickly (10 minutes) and are single-use.
- The authorization code is only valid with a corresponding PKCE verifier, which is generated and held in-memory by the client.
These mechanisms also prevent a lot of possible phishing-style attacks.
## Questions
- Is CORS sufficient for the cross-origin requests we're making? Any gotchas?
- How much do we want to style this new page?
- Should the page styling eventually be re-brandable (e.g., custom logo)?