# OAuth 2.0 Credential Support

This guide provides example implementations of OAuth 2.0 credentials to enable secure authentication in clients generated by TypeSpec.
With this approach, you can define OAuth 2.0 authentication directly in your TypeSpec files and supply custom credential implementations for a smooth, end-to-end authentication flow.

For more details, refer to the TypeSpec documentation: [Oauth2Auth](https://typespec.io/docs/libraries/http/reference/js-api/interfaces/oauth2auth/)

## Getting Started

- [Creating a client with OAuth Token](#create-custom-client-constructor-to-support-oauth-token)
- [Example implementation of AuthenticationTokenProvider](#example-implementation-of-authenticationtokenprovider)
- [Example usage of AuthenticationTokenProvider and Custom Client](#example-usage-of-authenticationtokenprovider-and-custom-client)

### Create custom client constructor to support OAuth token

This constructor initializes a new instance of the `SampleTypeSpecClient` class and sets a client pipeline with OAuth2 bearer token authentication using the provided `AuthenticationTokenProvider` and endpoint URI.

```csharp
using System.ClientModel;
using System.ClientModel.Primitives;

/// <summary>
/// Customize client to add custom constructor.
/// </summary>
public partial class SampleTypeSpecClient
{
  public SampleTypeSpecClient(Uri uri, AuthenticationTokenProvider tokenProvider)
  {
      _endpoint = uri;
      var options = new ClientPipelineOptions();
      Pipeline = ClientPipeline.Create(
          options,
          perCallPolicies: ReadOnlySpan<PipelinePolicy>.Empty,
          perTryPolicies: [new OAuth2BearerTokenAuthenticationPolicy(tokenProvider, flows)],
          beforeTransportPolicies: ReadOnlySpan<PipelinePolicy>.Empty
      );
  }
}
```

The full code is available [here](https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Custom/SampleTypeSpecClient.cs).

### Example implementation of AuthenticationTokenProvider

Below is an example implementation of the `AuthenticationTokenProvider` class that supports the client credentials flow.

```csharp
public class ClientCredentialTokenProvider : AuthenticationTokenProvider
{
    private string _clientId;
    private string _clientSecret;
    private HttpClient _client;

    public ClientCredentialTokenProvider(string clientId, string clientSecret)
    {
        _clientId = clientId;
        _clientSecret = clientSecret;
        _client = new HttpClient();
    }

    public override AuthenticationToken GetToken(GetTokenOptions properties, CancellationToken cancellationToken)
    {
        return GetAccessTokenInternal(false, properties, cancellationToken).GetAwaiter().GetResult();
    }

    public override async ValueTask<AuthenticationToken> GetTokenAsync(GetTokenOptions properties, CancellationToken cancellationToken)
    {
        return await GetAccessTokenInternal(true, properties, cancellationToken).ConfigureAwait(false);
    }

    public override GetTokenOptions? CreateTokenOptions(IReadOnlyDictionary<string, object> properties)
    {
        if (properties.TryGetValue(GetTokenOptions.ScopesPropertyName, out var scopes) && scopes is string[] scopeArray &&
            properties.TryGetValue(GetTokenOptions.TokenUrlPropertyName, out var tokenUri) && tokenUri is string tokenUriValue &&
            properties.TryGetValue(GetTokenOptions.RefreshUrlPropertyName, out var refreshUri) && refreshUri is string refreshUriValue)
        {
            return new GetTokenOptions(new Dictionary<string, object>
            {
                { GetTokenOptions.ScopesPropertyName, new ReadOnlyMemory<string>(scopeArray) },
                { GetTokenOptions.TokenUrlPropertyName, tokenUriValue },
                { GetTokenOptions.RefreshUrlPropertyName, refreshUriValue },
            });
        }
        return null;
    }

    internal async ValueTask<AuthenticationToken> GetAccessTokenInternal(bool async, GetTokenOptions properties, CancellationToken cancellationToken)
    {
        if (!properties.Properties.TryGetValue("tokenUrl", out var tokenUri) || tokenUri is not string tokenUriValue)
        {
            throw new ArgumentException("Argument properties did not contain the expected value 'tokenUrl'.", nameof(properties));
        }
        var request = new HttpRequestMessage(HttpMethod.Post, tokenUriValue);

        // Add Basic Authentication header
        var authBytes = System.Text.Encoding.ASCII.GetBytes($"{_clientId}:{_clientSecret}");
        var authHeader = Convert.ToBase64String(authBytes);
        request.Headers.Authorization = new AuthenticationHeaderValue("Basic", authHeader);
        var scopes = ExtractScopes(properties.Properties);

        // Create form content
        var formContent = new FormUrlEncodedContent(
        [
            new KeyValuePair<string, string>("grant_type", "client_credentials"),
            new KeyValuePair<string, string>("scope", string.Join(" ", scopes.ToArray()))
        ]);

        request.Content = formContent;

        using HttpResponseMessage response = async ?
            await _client.SendAsync(request) :
            _client.SendAsync(request).GetAwaiter().GetResult();

        response.EnsureSuccessStatusCode();

        // Deserialize the JSON response using System.Text.Json
        using var responseStream = await response.Content.ReadAsStreamAsync();
        using JsonDocument jsonDoc = await JsonDocument.ParseAsync(responseStream);
        JsonElement root = jsonDoc.RootElement;

        string? accessToken = root.GetProperty("access_token").GetString();
        string? tokenType = root.GetProperty("token_type").GetString();
        int expiresIn = root.GetProperty("expires_in").GetInt32();

        // Calculate expiration and refresh times based on current UTC time
        var now = DateTimeOffset.UtcNow;
        DateTimeOffset expiresOn = now.AddSeconds(expiresIn);
        DateTimeOffset refreshOn = now.AddSeconds(expiresIn * 0.85);

        return new AuthenticationToken(accessToken!, tokenType!, expiresOn, refreshOn);
    }

    private static ReadOnlyMemory<string> ExtractScopes(IReadOnlyDictionary<string, object> properties)
    {
        if (!properties.TryGetValue(GetTokenOptions.ScopesPropertyName, out var scopesValue) || scopesValue is null)
        {
            return ReadOnlyMemory<string>.Empty;
        }

        return scopesValue switch
        {
            ReadOnlyMemory<string> memory => memory,
            Memory<string> memory => memory,
            string[] array => new ReadOnlyMemory<string>(array),
            ICollection<string> collection => new ReadOnlyMemory<string>([.. collection]),
            IEnumerable<string> enumerable => new ReadOnlyMemory<string>([.. enumerable]),
            _ => ReadOnlyMemory<string>.Empty
        };
    }
}
```

### Example usage of AuthenticationTokenProvider and Custom Client

This example demonstrates how to use the `AuthenticationTokenProvider` with the custom client constructor to authenticate and make a request to the API.

```csharp
AuthenticationTokenProvider provider = new ClientCredentialTokenProvider("myClientId", "myClientSecret");
var client = new SampleTypeSpecClient(new Uri("http://localhost:5000"), provider);
client.Widget("myId");
```
