Federated User Authentication

Federated User Authentication (FUA) is a special case of using Open ID Connect to authenticate. Using FUA you can authenticate towards multiple TSM nodes using only one authentication at the identity provider. Basically the TSM nodes creates a payload, which is sign by the identity provider and the individual node will then be able to validate that it is actually a valid login.

CAVEAT: This method is only usable in the case where all nodes are controlled by a single instance of the SDK, and therefore not something we recommend in general.

It is possible to use OpenID Connect (OIDC), to log in to a TSM through an identity provider (IDP). At the moment Google, Microsoft, and Auth0 are known to work as IDPs of the TSM.

The log in flow starts with a call to the TSM, to retrieve some data, which has to be included in the authentication request send to the IDP.

var nodes []tsm.Node
for _, srv := range serverURLs {
	nodes = append(nodes, tsm.NewURLNode(*srv, tsm.NullAuthenticator{}))
}
client := tsm.NewClient(nodes)

oidcClient := tsm.NewOIDCClient(client)
idPNonce, oidcData, err := oidcClient.InitOIDC()

After obtaining the initial OIDC data, you call the authentication endpoint at the IDP, and set the nonce equal to the idPNonce from the call above (this example uses PKCE - Proof Key for Code Exchange):

authURL, err := url.Parse(idPServerURI + "/oauth2/auth")

query := authURL.Query()
query.Set("response_type", "id_token")
query.Set("scope", "openid%20email")
query.Set("client_secret", "some secret")
query.Set("redirect_uri", idPServerURI+"/callback")
query.Set("code_challenge", codeChallenge)
query.Set("code_challenge_method", "S256")
query.Set("nonce", idPNonce)
authURL.RawQuery = query.Encode()

resp, err := http.Get(authURL.String())

The result of the authentication request is a redirected call to the redirect_uri above (callback).

In the callback, you will receive a token from the IDP, containing, among other things, an idToken, which has been signed by the IDP. The idToken must be extracted and used as parameter, when obtaining a client for use with the TSM:

buf := new(strings.Builder)
_, err = io.Copy(buf, resp.Body)
token := buf.String()
decodedToken, err := base64.URLEncoding.DecodeString(token)
idToken, err := extractIDToken(decodedToken)
oidcAuthClient, err := tsm.NewClientFromIDToken(client.Nodes, idToken, oidcData)

Where the extractIDToken method is this:

func extractIDToken(identityProviderTokenRaw []byte) (string, error) {
	identityProviderToken := struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int    `json:"expires_in"`
		Scope       string `json:"scope"`
		TokenType   string `json:"token_type"`
		IDToken     string `json:"id_token"`
	}{}

	err := json.Unmarshal(identityProviderTokenRaw, &identityProviderToken)
	if err != nil {
		return "", fmt.Errorf("unable to parse identityProviderToken: %w", err)
	}

	return identityProviderToken.IDToken, nil
}

You should now be able to use the oidcAuthClient to access restricted methods on the TSM.