using System.Text; using IdentityModel.Client; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; namespace CH.KeycloakApi; public class KeycloakService { private readonly KeycloakSettings settings; public KeycloakService(IConfiguration configuration) { this.settings = new KeycloakSettings(); configuration.Bind("Keycloak", settings); } public KeycloakSettings Settings => settings; // use token manager instead public async Task GetTokenAsync(CancellationToken cancellationToken = default) { var tokenEndpoint = $"{this.settings.Endpoint}/realms/${settings.ApiRealm}/protocol/openid-connect/token"; var client = new HttpClient(); var response = await client.RequestTokenAsync(new TokenRequest { Address = tokenEndpoint, GrantType = "client_credentials", ClientId = this.settings.ClientId, ClientSecret = this.settings.ClientSecret, }, cancellationToken); return response.AccessToken; } public async Task GetUserByEmailAsync(string realm, string email) { var httpClient = new HttpClient(); var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users?email={email}"; httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var keycloakUsers = JsonConvert.DeserializeObject>(json); return keycloakUsers?.FirstOrDefault(); } public async Task GetUserByIdAsync(string realm, string id) { var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}"; var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var keycloakUser = JsonConvert.DeserializeObject(json); return keycloakUser; } public async Task SendChangePasswordEmailAsync(string realm, string id) { //PUT /{realm}/users/{id}/execute-actions-email var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}/execute-actions-email"; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); var putJson = JsonConvert.SerializeObject(new string[] { "UPDATE_PASSWORD" }); var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); } public async Task?> GetUsersAsync(string realm, string? search = null, int max = 100) { var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users?max={max}"; if (!string.IsNullOrWhiteSpace(search)) url += $"&search={search}"; var response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var keycloakUsers = JsonConvert.DeserializeObject>(json); return keycloakUsers; } public async Task ChangePasswordAsync(string realm, string id, string newPassword, bool temporary) { // auth/admin/realms/{realm}/users/{id}/reset-password /////{ "type": "password", "temporary": false, "value": "my-new-password" } var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}/reset-password"; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); var putJson = JsonConvert.SerializeObject(new { type = "password", temporary, value = newPassword }); var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); } public async Task UpdateUserByIdAsync(string realm, string id, string email, string firstName, string lastName, bool enabled) { var user = await GetUserByIdAsync(realm, id); if (user == null) throw new Exception($"no user {email} from on realm {realm}"); user.Email = email; user.FirstName = firstName; user.LastName = lastName; user.Username = email; user.Enabled = enabled; var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}"; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); var putJson = JsonConvert.SerializeObject(user); var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); } public async Task EmailExistsAsync(string realm, string email) { return await this.GetUserByEmailAsync(realm, email) != null; } public async Task CreateOrUpdateAsync(string realm, string email, string firstName, string lastName, bool enabled) { var existingUser = await GetUserByEmailAsync(realm, email); if (existingUser != null) { await UpdateUserByIdAsync(realm, existingUser.Id, email, firstName, lastName, enabled); return await GetUserByEmailAsync(realm, email); } return await CreateUserAsync(realm, email, firstName, lastName, enabled); } public async Task CreateUserAsync(string realm, string email, string firstName, string lastName, bool enabled) { long epochTicks = new DateTime(1970, 1, 1).Ticks; long unixTime = ((DateTime.UtcNow.Ticks - epochTicks) / TimeSpan.TicksPerSecond); var user = new KeycloakUser() { CreatedTimestamp = unixTime, Username = email, FirstName = firstName, LastName = lastName, Enabled = enabled, Totp = false, EmailVerified = false, RequiredActions = new List(), Attributes = null, Email = email, NotBefore = 0, Access = new Dictionary { { "manageGroupMembership", true }, { "view", true }, { "mapRoles", true }, { "impersonate", true }, { "manage", true } } }; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync()); var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users"; var postJson = JsonConvert.SerializeObject(user); var response = await httpClient.PostAsync(url, new StringContent(postJson, Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); var keycloakUser = await GetUserByEmailAsync(realm, email); return keycloakUser; } }