--- title: Using the Pinniped Supervisor to provide authentication for web applications description: Allow your Kubernetes cluster users to authenticate into web apps using the same identities. cascade: layout: docs menu: docs: name: Web Application Authentication weight: 800 parent: howtos --- The Pinniped Supervisor is an [OpenID Connect (OIDC)](https://openid.net/connect/) issuer that can be used to bring your user identities from an external identity provider into your Kubernetes clusters for all your `kubectl` users. It can also be used to bring those same identities to web applications that are intended for use by the same users. For example, a Kubernetes dashboard web application for cluster developers could use the Supervisor as its OIDC identity provider. This guide explains how to use the Supervisor to provide authentication services for a web application. Note that this feature is not part of how Pinniped provides authentication for `kubectl` users. By default, the Pinniped Supervisor will contain an OIDC client called `pinniped-cli` which requires no configuration and is used to provide authentication for `kubectl` (and other kubeconfig-based Kubernetes API clients). If you are only setting up authentication for `kubectl` users of your Kubernetes clusters, then you do not need to read this guide. If you want to use the Pinniped Supervisor to provide authentication services for a web application, then this guide is for you. ## Prerequisites This guide assumes that you have installed and configured the Pinniped Supervisor, and configured it with an external identity provider, as described in the other guides. This guide also assumes that you have a web application which supports configuring an OIDC provider for user authentication, or that you are developing such a web application. From the point of view of the Supervisor, your webapp is called a "client" ([as defined in the OAuth 2.0 spec](https://www.rfc-editor.org/rfc/rfc6749#section-1.1)). Typically, the web application should use the OIDC client support from its web application development framework (e.g. Spring, Rails, Django, etc.) to implement authentication. The Supervisor requires that: - Clients must use the [OIDC authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth). Clients must use `code` as the [response_type](https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationExamples) at the authorization endpoint. - Clients must use [PKCE](https://oauth.net/2/pkce/) during the authorization code flow. - Clients must be confidential clients, meaning that they have a client ID and client secret. Clients must use [client secret basic auth](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1) for authentication at the token endpoint. - Clients must use `query` as the [response_mode](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) at the authorization endpoint, or not specify the `response_mode` param, which defaults to `query`. - The client may optionally send an extra parameter on the initial authorization request to indicate which identity provider the user would like to use when authenticating. This parameter is called `pinniped_idp_name` and the value of the parameter should be set to the `displayName` of the identity provider as it was configured on the FederationDomain. When this parameter is not included, and when the FederationDomain was configured with explicit `identityProviders` in its spec, then the user will be prompted to choose an identity provider from the list of available identity providers by an interstitial web page during their login flow. The value of this parameter should be considered a hint and not a hard requirement, since the user could choose to alter or remove this query param from the authorization URL, and thus could use a different available identity provider from the FederationDomain to log in. This is not a security concern, since any successful login using any available identity provider from the FederationDomain's configuration is a valid and allowed user. Most web application frameworks offer all these capabilities in their OAuth2/OIDC libraries. ## Performance implications of using OIDCClients in the Supervisor The Pinniped Supervisor is an efficient application which typically does not use a lot of CPU and memory resources. Using the OIDCClient CR, as described below, will cause the Supervisor to perform bcrypt operations to validate the client's secret during authorization and refresh flows. While each of these bcrypt operations takes only about a quarter second of CPU time, in aggregate, when lots of users are perform authorization and refresh flows, these bcrypts will constitute the majority of the CPU usage of the Supervisor. The administrator of the Supervisor may need to adjust the Supervisor Deployment once they are familiar with usage patterns of their Supervisor. Very heavy usage by clients might result in the Supervisor pods reaching their cpu limit and being throttled, resulting in poor performance. This can be alleviated by adjusting the number of Pod replicas, and the CPU requests and limits on each Pod. ## Create an OIDCClient For each web application, the administrator of the Pinniped Supervisor will create an OIDCClient describing what that web application is allowed to do: ```yaml apiVersion: config.supervisor.pinniped.dev/v1alpha1 kind: OIDCClient metadata: # name must have client.oauth.pinniped.dev- prefix name: client.oauth.pinniped.dev-my-webapp-client namespace: supervisor # must be in the same namespace as the Supervisor spec: allowedRedirectURIs: - https://my-webapp.example.com/callback allowedGrantTypes: - authorization_code - refresh_token - urn:ietf:params:oauth:grant-type:token-exchange allowedScopes: - openid - offline_access - pinniped:request-audience - username - groups ``` If you've saved this into a file `my-oidc-client.yaml`, then install it into your cluster using: ```sh kubectl apply -f my-oidc-client.yaml ``` Do not share OIDCClients between multiple web applications. Each web application should have its own OIDCClient. The `name` of the OIDCClient will be the client ID used by the web application in the OIDC flows. The `allowedGrantTypes` and `allowedScopes` decides what the web application is allowed to do with respect to authentication. There are several typical combinations of these settings: 1. A web application which is allowed to use the Supervisor for authentication, and furthermore is allowed to authenticate into Kubernetes clusters and perform actions on behalf of the users (using the user's identity): ```yaml spec: allowedGrantTypes: - authorization_code - refresh_token - urn:ietf:params:oauth:grant-type:token-exchange allowedScopes: - openid - offline_access - pinniped:request-audience - username - groups ``` 2. A web application which is allowed to use the Supervisor for authentication, but cannot perform actions on Kubernetes clusters. ```yaml spec: allowedGrantTypes: - authorization_code - refresh_token allowedScopes: - openid - offline_access - username # "groups" can be excluded from this list when the webapp does # not need to see the group memberships of the users. - groups ``` 3. A web application which is allowed to use the Supervisor for authentication, but cannot see the username or group memberships of the authenticated users, and cannot perform actions on Kubernetes clusters. ```yaml spec: allowedGrantTypes: - authorization_code - refresh_token allowedScopes: - openid - offline_access ``` ## Create a client secret for the OIDCClient For each OIDCClient created by the Supervisor administrator, the administrator will also need to generate a client secret for the client. The client secrets are random strings auto-generated by the Supervisor upon request. The plaintext secret will only be returned once upon creation. ```sh cat <}}) tutorial, then the next sections will apply. ### Cluster-scoped ID tokens The ID token issued at the end of the authorization code flow contains the user's Kubernetes identity. However, this ID token is typically not used directly to provide authentication to the Kubernetes clusters' API servers. In a typical configuration, the Pinniped Concierge is installed on each workload cluster and is configured with a JWTAuthenticator resource to validate ID tokens issued by the Pinniped Supervisor. However, typically each workload cluster's JWTAuthenticator is configured to validate a unique audience value (`aud` claim) of the ID tokens. This ensures that an ID token which is used to access one workload cluster cannot also be used to access other workload clusters, to limit the impact of a leaked token. In this typical configuration, the client must make an extra API call to the Supervisor after the authorization code flow before it can access a particular workload cluster, in order to get a cluster-scoped ID token for a specific workload cluster (technically, for the audience value of that workload cluster).This request is made to the token endpoint, using parameters described in [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693). This request requires that the access token was granted the `username` and `pinniped:request-audience` scopes in the authorization code flow, and preferably was also granted the `groups` scope. It also requires that the client's OIDCClient configuration allows it to use the `urn:ietf:params:oauth:grant-type:token-exchange` grant type. The client has already called the Supervisor FederationDomain's `/.well-known/openid-configuration` discovery endpoint at the beginning of the authorization code flow, so the client is already aware of the location of the FederationDomain's token endpoint. The client makes an HTTPS request to the token endpoint to request a cluster-scoped ID token. The client sends its client ID and client secret as a basic auth header. It sends the Supervisor-issued access token as the `subject_token` param to identify the user's active session, along with the other required parameters. ``` POST /federation-domain-path/oauth2/token HTTP/1.1 Host: my-supervisor.example.com Content-Type: application/x-www-form-urlencoded Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW grant_type=urn:ietf:params:oauth:grant-type:token-exchange &subject_token= &subject_token_type=urn:ietf:params:oauth:token-type:access_token &requested_token_type=urn:ietf:params:oauth:token-type:jwt &audience= ``` A successful request will result in a `200 OK` response with a JSON body. One of the top-level keys in the returned JSON object will be `id_token`, and the value at that key will be the cluster-scoped ID token. This exchange is typically repeated for each workload cluster, right before the client needs to access the Kubernetes API of that workload cluster. ### mTLS client certificates Once the client has a cluster-scoped ID token for a particular workload cluster, the next step towards accessing the Kubernetes API of that workload cluster, in a typical configuration, is to request an mTLS client certificate from that workload cluster. The client certificate will act as the credential for the Kubernetes API server. This is done by making a request to the `/apis/login.concierge.pinniped.dev/v1alpha1/tokencredentialrequests` API of the Kubernetes API of that cluster. This API is an aggregated API hosted on the Kubernetes API server, but behind the scenes is actually served by the Pinniped Concierge. It can be accessed just like any other Kubernetes API. It does not require any authentication on the request. The details of the request and response formats are documented in the [API docs](https://github.com/vmware-tanzu/pinniped/blob/main/generated/{{< latestcodegenversion >}}/README.adoc#tokencredentialrequest). Here is a sample YAML representation of a request: ```yaml apiVersion: login.concierge.pinniped.dev/v1alpha1 kind: TokenCredentialRequest spec: token: authenticator: apiGroup: authentication.concierge.pinniped.dev kind: JWTAuthenticator name: ``` And here is a sample YAML representation of a successful response: ```yaml apiVersion: login.concierge.pinniped.dev/v1alpha1 kind: TokenCredentialRequest status: credential: expirationTimestamp: clientCertificateData: clientKeyData: ``` The returned mTLS client certificate will contain the user's identity (username and groups) copied from the cluster-scoped ID token. It may be used to make calls to the Kubernetes API as that user, until it expires. These mTLS client certificates are short-lived, typically good for about 5-15 minutes. After it expires, a client which wishes to make more Kubernetes API calls will need to perform an OIDC refresh request to the Supervisor to get a new access token, and then repeat the steps described above to get new cluster-scoped ID tokens and mTLS client certificates. Requiring these steps to be repeated often ensures that the user's session with the external identity provider is validated often, to ensure any changes to the user's level of access will quickly be reflected in the Kubernetes clusters.