With our growing fleet of Managed Services infrastructure , several of which integrate with each other and Sourcegraph.com, we have a growing need to securely manage how these services talk to each other. The OAuth2 standard offers a mechanism for this specific use case, called "client credentials grant", which is implemented by Sourcegraph Accounts Management System (SAMS) as an OAuth2-compliant service. We leverage this mechanism to offer client credential grants and act as the authorization server for all managed services machine-to-machine (M2M) traffic .

Adoption of this mechanism was originally proposed in https://docs.google.com/document/d/1FEtaQ9h06zy2J8oabWoV_DTQouV_1CobbVGf2nvuMGI/edit



Overview

The OAuth2 standard offers a mechanism for M2M auth, called "client credentials grant", that is widely used - a generic flow is described here. Managed services (including Sourcegraph.com, when managing communication with managed services) provision client credentials using SAMS, and use that to request an access token from SAMS (as the authorization server). The access token that SAMS provides can be provided when communicating with other services, and the recipient validates the token against SAMS via an introspection request (also an OAuth2 standard).

The following diagram presents a high-level overview of how this works.

sequenceDiagram
    participant S1 as Service 1
    participant S2 as Service 2
    participant SAMS as Sourcegraph Accounts
    autonumber
    
    rect rgba(0, 0, 255, .1)
        note right of S1: One-time manual setup
        create participant C as OAuth2 Client
        SAMS ->> C: Generate with<br>allowed scopes
        Note right of C: Stored in<br>SAMS database
        C ->> S1: Client credentials (client ID and client secret) to configure and use
    end

    S1 ->> SAMS: Client ID, client secret, and<br>requested scope "client.service2"
    Note right of S1: Requested scope to be<br>"a client of service 2",<br>or a more granular scope
    SAMS ->> C: Is "client.service2" an allowed scope<br>for these credentials?
    Note right of C: Client created by<br>teammate with allowed<br>scope "client.service2"<br>in this example
    C ->> SAMS: OK
    activate SAMS
    Note right of SAMS: Session created<br>and tracked in SAMS 
    SAMS ->> S1: Issued access token
    S1 ->> S2: Request resource<br>with access token
    S2 ->> SAMS: Introspect token
    SAMS ->> S2: Is valid, has scopes
    S2 ->> S2: Are these scopes sufficient<br>for this resource?
    S2 ->> S1: Resource
     

    deactivate SAMS

Authentication: token introspection

Servers should check the validity of an access token and the scopes associated with it using token introspection - this is provided in the SAMS SDK (‣) via [(TokenServiceV1).IntrospectToken](<https://pkg.go.dev/github.com/sourcegraph/sourcegraph-accounts-sdk-go#TokensServiceV1>). Introspection allows a server to check if the provided access token and its associated SAMS session is valid and active:

	result, err := tokens.IntrospectToken(ctx, token)
	if err != nil {
		// ...
	}

	// Active encapsulates whether the token is active, including expiration.
	if !result.Active {
		// ...
	}

Clients should emit their credentials when using the SAMS SDK to pass a server’s introspection check in ClientV1ConnConfig.

Authorization: allowed scopes and requested scopes

See SAMS token scope specification to learn more about how scopes are structured within SAMS. Scopes must be “allowed”, then “requested”, as follows:

Clients should request scopes they need required up-front when creating a SAMS client using the SAMS SDK (‣) scopes.Scope type:

requestedScopes := []scopes.Scope{/* ... */}
samsClient, err := sams.NewClientV1(config, requestedScopes)
if err != nil {
	// ...
}

Servers should integrate (scopes.AllowedScopes).Contains or (scopes.Scope).Match to validate incoming requested scopes according to strategies outlined in SAMS token scope specification, for example: