-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(API): add grpc API and service account authentication (#33)
Closes #8. This adds the possibility to authenticate via Service Accounts (with the JWT profile) and then use tokens in the API to call functions on zitadel.ch
- Loading branch information
Showing
33 changed files
with
8,630 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"version": 1, | ||
"isRoot": true, | ||
"tools": { | ||
"dotnet-grpc": { | ||
"version": "2.36.0", | ||
"commands": [ | ||
"dotnet-grpc" | ||
] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System.Collections.Generic; | ||
using Zitadel.Authentication; | ||
using Zitadel.Authentication.Credentials; | ||
|
||
namespace Zitadel.Api | ||
{ | ||
/// <summary> | ||
/// Options for an API client. | ||
/// </summary> | ||
public record ClientOptions | ||
{ | ||
/// <summary> | ||
/// The API endpoint for the client. This will be the base url for the api calls. | ||
/// </summary> | ||
public string Endpoint { get; init; } = ZitadelDefaults.ZitadelApiEndpoint; | ||
|
||
/// <summary> | ||
/// The organizational context in the API. This essentially defines the "x-zitadel-orgid" header value | ||
/// which provides the api with the orgId that the API call will be executed in. | ||
/// This may be overwritten for specific calls. | ||
/// </summary> | ||
public string Organization { get; init; } = string.Empty; | ||
|
||
/// <summary> | ||
/// Authentication token for the client. This field may not be used in conjunction with | ||
/// <see cref="ServiceAccountAuthentication"/>. Use this field to explicitly set the | ||
/// Bearer token that will be transmitted to the API. If no authentication method is set, | ||
/// each call must attach the authorization header. | ||
/// </summary> | ||
public string? Token { get; init; } | ||
|
||
/// <summary> | ||
/// Service Account authentication method. If this field is set, the API calls are | ||
/// automatically authenticated with a <see cref="ServiceAccount"/> and the corresponding | ||
/// <see cref="ServiceAccount.AuthOptions"/>. This will renew the access token if it is | ||
/// expired. | ||
/// </summary> | ||
public (ServiceAccount Account, ServiceAccount.AuthOptions AuthOptions)? ServiceAccountAuthentication | ||
{ | ||
get; | ||
init; | ||
} | ||
|
||
/// <summary> | ||
/// List of additional arbitrary headers that are attached to each call. | ||
/// </summary> | ||
public IDictionary<string, string>? AdditionalHeaders { get; init; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
using System; | ||
using System.Net.Http; | ||
using Caos.Zitadel.Admin.Api.V1; | ||
using Caos.Zitadel.Auth.Api.V1; | ||
using Caos.Zitadel.Management.Api.V1; | ||
using Grpc.Core; | ||
using Grpc.Net.Client; | ||
using Zitadel.Authentication; | ||
|
||
namespace Zitadel.Api | ||
{ | ||
/// <summary> | ||
/// Helper class to instantiate api service clients for the zitadel API with correct settings. | ||
/// </summary> | ||
public static class Clients | ||
{ | ||
/// <summary> | ||
/// Create a service client for the auth service. | ||
/// </summary> | ||
/// <param name="options">Options for the client like authorization method.</param> | ||
/// <returns>The <see cref="Caos.Zitadel.Auth.Api.V1.AuthService.AuthServiceClient"/>.</returns> | ||
public static AuthService.AuthServiceClient AuthService(ClientOptions options) => | ||
GetClient<AuthService.AuthServiceClient>(options); | ||
|
||
/// <summary> | ||
/// Create a service client for the admin service. | ||
/// </summary> | ||
/// <param name="options">Options for the client like authorization method.</param> | ||
/// <returns>The <see cref="Caos.Zitadel.Admin.Api.V1.AdminService.AdminServiceClient"/>.</returns> | ||
public static AdminService.AdminServiceClient AdminService(ClientOptions options) => | ||
GetClient<AdminService.AdminServiceClient>(options); | ||
|
||
/// <summary> | ||
/// Create a service client for the management service. | ||
/// </summary> | ||
/// <param name="options">Options for the client like authorization method.</param> | ||
/// <returns>The <see cref="Caos.Zitadel.Management.Api.V1.ManagementService.ManagementServiceClient"/>.</returns> | ||
public static ManagementService.ManagementServiceClient ManagementService(ClientOptions options) => | ||
GetClient<ManagementService.ManagementServiceClient>(options); | ||
|
||
private static TClient GetClient<TClient>(ClientOptions options) | ||
where TClient : ClientBase<TClient> | ||
{ | ||
var httpClient = options.Token == null && options.ServiceAccountAuthentication != null | ||
? new HttpClient( | ||
new ServiceAccountHttpHandler( | ||
options.ServiceAccountAuthentication.Value.Account, | ||
options.ServiceAccountAuthentication.Value.AuthOptions)) | ||
: new HttpClient(); | ||
|
||
httpClient.DefaultRequestHeaders.Add(ZitadelDefaults.ZitadelOrgIdHeader, options.Organization); | ||
|
||
if (options.Token != null) | ||
{ | ||
httpClient.DefaultRequestHeaders.Authorization = new("Bearer", options.Token); | ||
} | ||
|
||
if (options.AdditionalHeaders != null) | ||
{ | ||
foreach (var (name, value) in options.AdditionalHeaders) | ||
{ | ||
httpClient.DefaultRequestHeaders.Add(name, value); | ||
} | ||
} | ||
|
||
var channel = GrpcChannel.ForAddress( | ||
options.Endpoint, | ||
new GrpcChannelOptions { HttpClient = httpClient }); | ||
var serviceType = typeof(TClient); | ||
|
||
return Activator.CreateInstance(serviceType, channel) as TClient ?? | ||
throw new($"Could not instantiate type {serviceType}"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Zitadel.Authentication.Credentials; | ||
|
||
namespace Zitadel.Api | ||
{ | ||
internal class ServiceAccountHttpHandler : DelegatingHandler | ||
{ | ||
private static readonly TimeSpan ServiceTokenLifetime = TimeSpan.FromHours(12); | ||
|
||
private readonly ServiceAccount _account; | ||
private readonly ServiceAccount.AuthOptions _options; | ||
|
||
private DateTime _tokenExpiryDate; | ||
private string? _token; | ||
|
||
public ServiceAccountHttpHandler(ServiceAccount account, ServiceAccount.AuthOptions options) | ||
: base(new HttpClientHandler()) | ||
{ | ||
_account = account; | ||
_options = options; | ||
} | ||
|
||
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) | ||
=> SendAsync(request, cancellationToken).Result; | ||
|
||
protected override async Task<HttpResponseMessage> SendAsync( | ||
HttpRequestMessage request, | ||
CancellationToken cancellationToken) | ||
{ | ||
if (request.Headers.Authorization != null) | ||
{ | ||
return await base.SendAsync(request, cancellationToken); | ||
} | ||
|
||
// When the token is not fetched or it is expired, re-fetch a service account token. | ||
if (_token == null || _tokenExpiryDate < DateTime.UtcNow) | ||
{ | ||
_token = await _account.AuthenticateAsync(_options); | ||
_tokenExpiryDate = DateTime.UtcNow + ServiceTokenLifetime; | ||
} | ||
|
||
request.Headers.Authorization = new("Bearer", _token); | ||
return await base.SendAsync(request, cancellationToken); | ||
} | ||
} | ||
} |
Oops, something went wrong.