OAuth2 Client Credentials Flow for Workloads

WORKLOAD Users (read more about User management here) can use the standard OAuth2 client credentials authentication flow (read more here) to authenticate themselves to the Cluster and start accessing its publicly exposed HTTP-based Services (read more about publicly exposed BeyondCorp Services here), such as HTTP/gRPC-based APIs or Kubernetes clusters, exactly like any protected public SaaS HTTP-based resource without having to install clients on their hosts, use special SDKs or even having to be aware of the Cluster's existence at all. This allows you to write applications in any programming language and use standard OAuth2 libraries to securely access all the Cluster's publicly exposed Services via a single identity and credential.

note

In addition to using OAuth2 client credentials flow Credentials, you can also generate an access token Credential and use it directly as a bearer token to access publicly exposed Services. Read more here.

It simply works as follows:

  1. Obtain an OAuth2 client credential Credential (read more here) as follows:

octeliumctl create cred --user root --type oauth2 my-oauth-cred # Here is the command output Client ID: nz7y-hagw Client Secret: AQpA691Z...

Now you can use the client credentials, for example, within your application to authenticate to the Cluster's OAuth2 token endpoint located at the URL https://<DOMAIN>/oauth2/token. For example, let's assume we want to access the Service my-api and obtain the access token that can then be used to access your Services via the standard bearer authentication (i.e. via the HTTP request header Authorization: Bearer <ACCESS_TOKEN>).

Using shell and curl, this can be simply done as follows:

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&grant_type=client_credentials' 'https://<DOMAIN>/oauth2/token' # Now we use the obtained access token from the above request to access the Service curl -H "Authorization: Bearer <ACCESS_TOKEN>" https://my-api.<DOMAIN>

Here is an equivalent example in Golang:

package main import ( "context" "fmt" "io" "golang.org/x/oauth2/clientcredentials" ) func main() { if err := doMain(context.Background()); err != nil { panic(err) } } func doMain(ctx context.Context) error { // Your Cluster domain domain := "example.com" // Configuration for the client credentials flow config := clientcredentials.Config{ ClientID: "y56y-9ru3", ClientSecret: "AQpAt200_CHp2S9G...", TokenURL: fmt.Sprintf("https://%s/oauth2/token", domain), } // Now we obtain an access token from our OAuth2 client credentials _, err := config.Token(ctx) if err != nil { return err } // Now we access our Service "my-api" client := config.Client(ctx) resp, err := client.Get(fmt.Sprintf("https://my-api.%s", domain)) if err != nil { return err } defer resp.Body.Close() bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return err } fmt.Printf("Response: %s\n", string(bodyBytes)) return nil }

And here is an equivalent example in Typescript:

import axios from "axios"; import { URLSearchParams } from "url"; const clientId = "y56y-9ru3"; const clientSecret = "AQpAyteKC0UPDI1U..."; const domain = "<DOMAIN>"; const getAccessToken = async (): Promise<string> => { try { const params = new URLSearchParams(); params.append("grant_type", "client_credentials"); params.append("client_id", clientId); params.append("client_secret", clientSecret); const response = await axios.post( `https://${domain}/oauth2/token`, params, { headers: { "Content-Type": "application/x-www-form-urlencoded", }, } ); return response.data.access_token; } catch (error) { console.error("Error fetching access token:", error); throw error; } }; const accessProtectedResource = async (token: string) => { try { const response = await axios.get(`https://my-api.${domain}`, { headers: { Authorization: `Bearer ${token}`, }, }); console.log(response) } catch (error) { throw error; } }; const main = async () => { const accessToken = await getAccessToken(); await accessProtectedResource(accessToken); }; main();
note

You can optionally use Octelium scopes as OAuth2 scopes. Read more about scopes here.

There are standard libraries in almost all the major programming languages to use the OAuth2 client credentials flow and obtain the access token. Some examples are:

note

You can also use the issued access token in the X-Octelium-Auth: <ACCESS_TOKEN> header instead of using it in the typical Authorization: Bearer <ACCESS_TOKEN> header.

Workload Identity Federation

You can also use OpenID Connect JWT-based assertions in OAuth2 client credentials authentication, as defined in RFC 7523 to avoid having to issue OAuth2 client credential Credentials. This allows WORKLOAD Users to authenticate themselves using OIDC identity tokens issued by the identity provider hosting the workload ( e.g. Azure, GitHub Actions, Kubernetes clusters, SPIFFE, etc...) and automatically access the Cluster's HTTP-based resources in a "secretless" way that does not require issuing and distributing OAuth2 client credential Credentials as shown above. Here is an example in Golang but you can apply the same flow in any language:

package main import ( "context" "fmt" "io" "net/http" "golang.org/x/oauth2/clientcredentials" ) func main() { ctx := context.Background() domain := "example.com" // 1. Configure the OAuth2 Client Credentials exchange config := clientcredentials.Config{ TokenURL: fmt.Sprintf("https://%s/oauth2/token", domain), EndpointParams: map[string][]string{ "client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}, "client_assertion": {"<YOUR_JWT>"}, }, } // 2. Create an HTTP Client with your OAuth2 client credentials configuration httpClient := config.Client(ctx) // 3. Access the "demo-nginx" Service resp, err := httpClient.Get(fmt.Sprintf("https://demo-nginx.%s/", domain)) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { panic(err) } fmt.Printf("Response: %s\n", string(body)) }