✨ Add microsoft OIDC
This commit is contained in:
parent
b27b6b8c1b
commit
47caff569d
113
DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs
Normal file
113
DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Auth.OpenId;
|
||||||
|
|
||||||
|
public class MicrosoftOidcService : OidcService
|
||||||
|
{
|
||||||
|
public MicrosoftOidcService(IConfiguration configuration, IHttpClientFactory httpClientFactory, AppDatabase db)
|
||||||
|
: base(configuration, httpClientFactory, db)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ProviderName => "Microsoft";
|
||||||
|
|
||||||
|
protected override string DiscoveryEndpoint => _configuration[$"Oidc:{ConfigSectionName}:DiscoveryEndpoint"] ?? throw new InvalidOperationException("Microsoft OIDC discovery endpoint is not configured.");
|
||||||
|
|
||||||
|
protected override string ConfigSectionName => "Microsoft";
|
||||||
|
|
||||||
|
public override string GetAuthorizationUrl(string state, string nonce)
|
||||||
|
{
|
||||||
|
var config = GetProviderConfig();
|
||||||
|
var discoveryDocument = GetDiscoveryDocumentAsync().GetAwaiter().GetResult();
|
||||||
|
if (discoveryDocument?.AuthorizationEndpoint == null)
|
||||||
|
throw new InvalidOperationException("Authorization endpoint not found in discovery document.");
|
||||||
|
|
||||||
|
var queryParams = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "client_id", config.ClientId },
|
||||||
|
{ "response_type", "code" },
|
||||||
|
{ "redirect_uri", config.RedirectUri },
|
||||||
|
{ "response_mode", "query" },
|
||||||
|
{ "scope", "openid profile email" },
|
||||||
|
{ "state", state },
|
||||||
|
{ "nonce", nonce },
|
||||||
|
};
|
||||||
|
|
||||||
|
var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
|
||||||
|
return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<OidcUserInfo> ProcessCallbackAsync(OidcCallbackData callbackData)
|
||||||
|
{
|
||||||
|
var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code);
|
||||||
|
if (tokenResponse?.AccessToken == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to obtain access token from Microsoft");
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken);
|
||||||
|
|
||||||
|
userInfo.AccessToken = tokenResponse.AccessToken;
|
||||||
|
userInfo.RefreshToken = tokenResponse.RefreshToken;
|
||||||
|
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<OidcTokenResponse?> ExchangeCodeForTokensAsync(string code, string? codeVerifier = null)
|
||||||
|
{
|
||||||
|
var config = GetProviderConfig();
|
||||||
|
var discoveryDocument = await GetDiscoveryDocumentAsync();
|
||||||
|
if (discoveryDocument?.TokenEndpoint == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Token endpoint not found in discovery document.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
|
||||||
|
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, discoveryDocument.TokenEndpoint)
|
||||||
|
{
|
||||||
|
Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "client_id", config.ClientId },
|
||||||
|
{ "scope", "openid profile email" },
|
||||||
|
{ "code", code },
|
||||||
|
{ "redirect_uri", config.RedirectUri },
|
||||||
|
{ "grant_type", "authorization_code" },
|
||||||
|
{ "client_secret", config.ClientSecret },
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await client.SendAsync(tokenRequest);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return await response.Content.ReadFromJsonAsync<OidcTokenResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OidcUserInfo> GetUserInfoAsync(string accessToken)
|
||||||
|
{
|
||||||
|
var discoveryDocument = await GetDiscoveryDocumentAsync();
|
||||||
|
if (discoveryDocument?.UserinfoEndpoint == null)
|
||||||
|
throw new InvalidOperationException("Userinfo endpoint not found in discovery document.");
|
||||||
|
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, discoveryDocument.UserinfoEndpoint);
|
||||||
|
request.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
|
var microsoftUser = JsonDocument.Parse(json).RootElement;
|
||||||
|
|
||||||
|
return new OidcUserInfo
|
||||||
|
{
|
||||||
|
UserId = microsoftUser.GetProperty("sub").GetString() ?? "",
|
||||||
|
Email = microsoftUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null,
|
||||||
|
DisplayName = microsoftUser.TryGetProperty("name", out var nameElement) ? nameElement.GetString() ?? "" : "",
|
||||||
|
PreferredUsername = microsoftUser.TryGetProperty("preferred_username", out var preferredUsernameElement) ? preferredUsernameElement.GetString() ?? "" : "",
|
||||||
|
ProfilePictureUrl = microsoftUser.TryGetProperty("picture", out var pictureElement) ? pictureElement.GetString() ?? "" : "",
|
||||||
|
Provider = ProviderName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,7 @@ public abstract class OidcService
|
|||||||
{
|
{
|
||||||
ClientId = _configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "",
|
ClientId = _configuration[$"Oidc:{ConfigSectionName}:ClientId"] ?? "",
|
||||||
ClientSecret = _configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "",
|
ClientSecret = _configuration[$"Oidc:{ConfigSectionName}:ClientSecret"] ?? "",
|
||||||
RedirectUri = _configuration["BaseUrl"] + "/auth/callback/" + ProviderName
|
RedirectUri = _configuration["BaseUrl"] + "/auth/callback/" + ProviderName.ToLower()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +94,7 @@ builder.Services.AddHttpClient();
|
|||||||
builder.Services.AddScoped<OidcService, GoogleOidcService>();
|
builder.Services.AddScoped<OidcService, GoogleOidcService>();
|
||||||
builder.Services.AddScoped<OidcService, AppleOidcService>();
|
builder.Services.AddScoped<OidcService, AppleOidcService>();
|
||||||
builder.Services.AddScoped<OidcService, GitHubOidcService>();
|
builder.Services.AddScoped<OidcService, GitHubOidcService>();
|
||||||
|
builder.Services.AddScoped<OidcService, MicrosoftOidcService>();
|
||||||
builder.Services.AddScoped<OidcService, DiscordOidcService>();
|
builder.Services.AddScoped<OidcService, DiscordOidcService>();
|
||||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||||
{
|
{
|
||||||
|
@ -94,6 +94,11 @@
|
|||||||
"TeamId": "W7HPZ53V6B",
|
"TeamId": "W7HPZ53V6B",
|
||||||
"KeyId": "B668YP4KBG",
|
"KeyId": "B668YP4KBG",
|
||||||
"PrivateKeyPath": "./Keys/Solarpass.p8"
|
"PrivateKeyPath": "./Keys/Solarpass.p8"
|
||||||
|
},
|
||||||
|
"Microsoft": {
|
||||||
|
"ClientId": "YOUR_MICROSOFT_CLIENT_ID",
|
||||||
|
"ClientSecret": "YOUR_MICROSOFT_CLIENT_SECRET",
|
||||||
|
"DiscoveryEndpoint": "YOUR_MICROSOFT_DISCOVERY_ENDPOINT"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"KnownProxies": [
|
"KnownProxies": [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user