- Added async GetAuthorizationUrlAsync() methods to all OIDC providers - Updated base OidcService with abstract async contract and backward-compatible sync wrapper - Modified OidcController to use async authorization URL generation - Removed sync blocks using .GetAwaiter().GetResult() in Google provider - Maintained backward compatibility with existing sync method calls - Eliminated thread blocking and improved async flow throughout auth pipeline - Enhanced scalability by allowing non-blocking async authorization URL generation
		
			
				
	
	
		
			145 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.Text.Json;
 | 
						|
using DysonNetwork.Shared.Cache;
 | 
						|
 | 
						|
namespace DysonNetwork.Pass.Auth.OpenId;
 | 
						|
 | 
						|
public class MicrosoftOidcService(
 | 
						|
    IConfiguration configuration,
 | 
						|
    IHttpClientFactory httpClientFactory,
 | 
						|
    AppDatabase db,
 | 
						|
    AuthService auth,
 | 
						|
    ICacheService cache
 | 
						|
)
 | 
						|
    : OidcService(configuration, httpClientFactory, db, auth, cache)
 | 
						|
{
 | 
						|
    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 async Task<string> GetAuthorizationUrlAsync(string state, string nonce)
 | 
						|
    {
 | 
						|
        var config = GetProviderConfig();
 | 
						|
        var discoveryDocument = await GetDiscoveryDocumentAsync();
 | 
						|
 | 
						|
        if (discoveryDocument?.AuthorizationEndpoint == null)
 | 
						|
            throw new InvalidOperationException("Authorization endpoint not found in discovery document.");
 | 
						|
 | 
						|
        var queryParams = BuildAuthorizationParameters(
 | 
						|
            config.ClientId,
 | 
						|
            config.RedirectUri,
 | 
						|
            "openid profile email",
 | 
						|
            "code",
 | 
						|
            state,
 | 
						|
            nonce
 | 
						|
        );
 | 
						|
 | 
						|
        var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
 | 
						|
        return $"{discoveryDocument.AuthorizationEndpoint}?{queryString}";
 | 
						|
    }
 | 
						|
 | 
						|
    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
 | 
						|
        };
 | 
						|
    }
 | 
						|
}
 |