برنامه نویسی

NET MAUI Google Drive OAuth در ویندوز و اندروید

Summarize this content to 400 words in Persian Lang

Google Cloud Console

اگر پروژه هنوز وجود ندارد ایجاد کنید
پروژه خود را انتخاب کنید

صفحه رضایت OAuth را تنظیم کنید

مطمئن شوید که کاربر آزمایشی خود را اضافه کنید

Windows OAuth Client ID

Universal Windows Platform (UWP) را انتخاب کنید
شناسه فروشگاه را برای آزمایش تنظیم کنید. برای یک برنامه واقعی باید متفاوت باشد

شناسه مشتری OAuth Android

اندروید را انتخاب کنید
نام بسته را مانند شناسه برنامه پروژه تنظیم کنید
اثر انگشت گواهی SHA-1 را روی تنظیم کنید 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00. برای یک اپلیکیشن واقعی باید خودتان آن را بسازید. برای ویندوز، می توانید جاوا را نصب کنید که دارای ابزار کلیدی در پوشه bin است که می توانید از آن استفاده کنید.
طرح URI سفارشی فعال شد

پروژه NET MAUI

از یک پروژه جدید شروع کنید

بسته های NuGet زیر را نصب کنید

Google.Apis.Auth
Google.Apis.Drive.v3
Google.Apis.Oauth2.v2

اندروید را راه اندازی کنید

فایل WebAuthenticatorCallbackActivity.cs را به پوشه Platform/Android با محتوای زیر اضافه کنید:

using Android.App;
using Android.Content;
using Android.Content.PM;

namespace OAuthSample.Platforms.Android;

[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)] [IntentFilter(new[] { Intent.ActionView },
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataScheme = CALLBACK_SCHEME)] public class WebAuthenticationCallbackActivity : Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity
{

const string CALLBACK_SCHEME = “com.companyname.oauthsample”;
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سرویس GoogleDrive را راه اندازی کنید

فایل GoogleDriveService.cs را با محتوای زیر به پوشه Services اضافه کنید (شناسه سرویس گیرنده UWP و اندروید خود را در بالا تنظیم کنید):

using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Oauth2.v2;
using Google.Apis.Services;
using System.Diagnostics;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Nodes;

namespace OAuthSample.Services;

public class GoogleDriveService
{
readonly string _windowsClientId = “__UWP_CLIENT_ID_HERE__”; // UWP client
readonly string _androidClientId = “__ANDROID_CLIENT_ID_HERE__”; // Android client

Oauth2Service? _oauth2Service;
DriveService? _driveService;
GoogleCredential? _credential;
string? _email;

public bool IsSignedIn => _credential != null;
public string? Email => _email;

public async Task Init()
{
var hasRefreshToken = await SecureStorage.GetAsync(“refresh_token”) is not null;
if (!IsSignedIn && hasRefreshToken)
{
await SignIn();
}
}

public async Task SignIn()
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var expiresIn = Preferences.Get(“access_token_epires_in”, 0L);
var isExpired = now – 10 > expiresIn; // 10 second buffer
var hasRefreshToken = await SecureStorage.GetAsync(“refresh_token”) is not null;

if (isExpired && hasRefreshToken)
{
Debug.WriteLine(“Using refresh token”);
await RefreshToken();
}
else if (isExpired) // No refresh token
{
Debug.WriteLine(“Starting auth code flow”);
if (DeviceInfo.Current.Platform == DevicePlatform.WinUI)
{
await DoAuthCodeFlowWindows();
}
else if (DeviceInfo.Current.Platform == DevicePlatform.Android)
{
await DoAuthCodeFlowAndroid();
}
else
{
throw new NotImplementedException($”Auth flow for platform {DeviceInfo.Current.Platform} not implemented.”);
}
}

var accesToken = await SecureStorage.GetAsync(“access_token”);
_credential = GoogleCredential.FromAccessToken(accesToken);
_oauth2Service = new Oauth2Service(new BaseClientService.Initializer
{
HttpClientInitializer = _credential,
ApplicationName = “yeetmedia3”
});
_driveService = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = _credential,
ApplicationName = “yeetmedia3”
});
var userInfo = await _oauth2Service.Userinfo.Get().ExecuteAsync();
_email = userInfo.Email;
}

public async Task ListFiles()
{
var request = _driveService!.Files.List();
var fileList = await request.ExecuteAsync();
var stringBuilder = new StringBuilder();

stringBuilder.AppendLine(“Files:”);
stringBuilder.AppendLine();
if (fileList.Files != null && fileList.Files.Count > 0)
{
foreach (var file in fileList.Files)
{
stringBuilder.AppendLine($”Files: {file.Name} ({file.Id}”);
}
}
else
{
stringBuilder.AppendLine(“No files found.”);
}
return stringBuilder.ToString();
}

public async Task SignOut()
{
await RevokeTokens();
}

private async Task DoAuthCodeFlowWindows()
{
var authUrl = “https://accounts.google.com/o/oauth2/v2/auth”;
var clientId = _windowsClientId;
var localPort = 12345;
var redirectUri = $”http://localhost:{localPort}”;
var codeVerifier = GenerateCodeVerifier();
var codeChallenge = GenerateCodeChallenge(codeVerifier);
var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);
var queryString = string.Join(“&”, parameters.Select(param => $”{param.Key}={param.Value}”));
var fullAuthUrl = $”{authUrl}?{queryString}”;

await Launcher.OpenAsync(fullAuthUrl);
var authorizationCode = await StartLocalHttpServerAsync(localPort);

await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);
}

private async Task DoAuthCodeFlowAndroid()
{
var authUrl = “https://accounts.google.com/o/oauth2/v2/auth”;
var clientId = _androidClientId;
var redirectUri = “com.companyname.yeetmedia3://”; // requires a period: https://developers.google.com/identity/protocols/oauth2/native-app#android
var codeVerifier = GenerateCodeVerifier();
var codeChallenge = GenerateCodeChallenge(codeVerifier);
var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);
var queryString = string.Join(“&”, parameters.Select(param => $”{param.Key}={param.Value}”));
var fullAuthUrl = $”{authUrl}?{queryString}”;
#pragma warning disable CA1416
var authCodeResponse = await WebAuthenticator.AuthenticateAsync(new Uri(fullAuthUrl), new Uri(“com.companyname.yeetmedia3://”));
#pragma warning restore CA1416
var authorizationCode = authCodeResponse.Properties[“code”];

await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);
}

private static Dictionary GenerateAuthParameters(string redirectUri, string clientId, string codeChallenge)
{
return new Dictionary
{
//{ “scope”, “https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.appdata” },
{ “scope”, string.Join(‘ ‘, [Oauth2Service.Scope.UserinfoProfile, Oauth2Service.Scope.UserinfoEmail, DriveService.Scope.Drive, DriveService.Scope.DriveFile, DriveService.Scope.DriveAppdata]) },
{ “access_type”, “offline” },
{ “include_granted_scopes”, “true” },
{ “response_type”, “code” },
//{ “state”, “state_parameter_passthrough_value” },
{ “redirect_uri”, redirectUri },
{ “client_id”, clientId },
{ “code_challenge_method”, “S256” },
{ “code_challenge”, codeChallenge },
//{ “prompt”, “consent” }
};
}

private static async Task GetInitialToken(string authorizationCode, string redirectUri, string clientId, string codeVerifier)
{
var tokenEndpoint = “https://oauth2.googleapis.com/token”;
var client = new HttpClient();
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
{
Content = new FormUrlEncodedContent(
[
new KeyValuePair(“grant_type”, “authorization_code”),
new KeyValuePair(“code”, authorizationCode),
new KeyValuePair(“redirect_uri”, redirectUri),
new KeyValuePair(“client_id”, clientId),
new KeyValuePair(“code_verifier”, codeVerifier)
])
};

var response = await client.SendAsync(tokenRequest);
var responseBody = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode) throw new Exception($”Error requesting token: {responseBody}”);

Debug.WriteLine($”Access token: {responseBody}”);
var jsonToken = JsonObject.Parse(responseBody);
var accessToken = jsonToken![“access_token”]!.ToString();
var refreshToken = jsonToken![“refresh_token”]!.ToString();
var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken![“expires_in”]!.ToString());
await SecureStorage.SetAsync(“access_token”, accessToken);
await SecureStorage.SetAsync(“refresh_token”, refreshToken);
Preferences.Set(“access_token_epires_in”, accessTokenExpiresIn);
}

private async Task RefreshToken()
{
var clientId = DeviceInfo.Current.Platform == DevicePlatform.WinUI ? _windowsClientId : _androidClientId;
var tokenEndpoint = “https://oauth2.googleapis.com/token”;
var refreshToken = await SecureStorage.GetAsync(“refresh_token”);
var client = new HttpClient();
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
{
Content = new FormUrlEncodedContent(
[
new KeyValuePair(“client_id”, clientId),
new KeyValuePair(“grant_type”, “refresh_token”),
new KeyValuePair(“refresh_token”, refreshToken!)
] )
};

var response = await client.SendAsync(tokenRequest);
var responseBody = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode) throw new Exception($”Error requesting token: {responseBody}”);

Debug.WriteLine($”Refresh token: {responseBody}”);
var jsonToken = JsonObject.Parse(responseBody);
var accessToken = jsonToken![“access_token”]!.ToString();
var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken![“expires_in”]!.ToString());
await SecureStorage.SetAsync(“access_token”, accessToken);
Preferences.Set(“access_token_epires_in”, accessTokenExpiresIn);
}

private async Task RevokeTokens()
{
var revokeEndpoint = “https://oauth2.googleapis.com/revoke”;
var access_token = await SecureStorage.GetAsync(“access_token”);
var client = new HttpClient();
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, revokeEndpoint)
{
Content = new FormUrlEncodedContent(
[
new KeyValuePair(“token”, access_token!),
] )
};

var response = await client.SendAsync(tokenRequest);
var responseBody = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode) throw new Exception($”Error revoking token: {responseBody}”);

Debug.WriteLine($”Revoke token: {responseBody}”);
SecureStorage.Remove(“access_token”);
SecureStorage.Remove(“refresh_token”);
Preferences.Remove(“access_token_epires_in”);

_credential = null;
_oauth2Service = null;
_driveService = null;
}

private static async Task StartLocalHttpServerAsync(int port)
{
var listener = new HttpListener();
listener.Prefixes.Add($”http://localhost:{port}/”);
listener.Start();

Debug.WriteLine($”Listening on http://localhost:{port}/…”);
var context = await listener.GetContextAsync();

var code = context.Request.QueryString[“code”];
var response = context.Response;
var responseString = “Authorization complete. You can close this window.”;
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer);
response.OutputStream.Close();

listener.Stop();

if (code is null) throw new Exception(“Auth ode not returned”);

return code;
}

private static string GenerateCodeVerifier()
{
using var rng = RandomNumberGenerator.Create();
var bytes = new byte[32]; // Length can vary, e.g., 43-128 characters
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes)
.TrimEnd(‘=’)
.Replace(‘+’, ‘-‘)
.Replace(“https://dev.to/”, ‘_’);
}

private static string GenerateCodeChallenge(string codeVerifier)
{
var hash = SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier));
return Convert.ToBase64String(hash)
.TrimEnd(‘=’)
.Replace(‘+’, ‘-‘)
.Replace(“https://dev.to/”, ‘_’);
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

MainPage.xaml را به موارد زیر به روز کنید:

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

MainPage.xaml.cs را به موارد زیر به روز کنید:

using OAuthSample.Services;

namespace OAuthSample;

public partial class MainPage : ContentPage
{
readonly GoogleDriveService _googleDriveService = new();
public MainPage()
{
InitializeComponent();
}

private async void ContentPage_Loaded(object sender, EventArgs e)
{
await _googleDriveService.Init();
UpdateButton();
}

private async void SignIn_Clicked(object sender, EventArgs e)
{
if (SignInButton.Text == “Sign In”)
{
await _googleDriveService.SignIn();
}
else
{
await _googleDriveService.SignOut();

}
UpdateButton();
}

private async void List_Clicked(object sender, EventArgs e)
{
ListLabel.Text = await _googleDriveService.ListFiles();
}

private void UpdateButton()
{
if (_googleDriveService.IsSignedIn)
{
SignInButton.Text = $”Sign Out ({_googleDriveService.Email})”;
ListButton.IsVisible = true;
}
else
{
SignInButton.Text = “Sign In”;
ListButton.IsVisible = false;
ListLabel.Text = String.Empty;
}
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

(اختیاری) اندازه پنجره را برای ویندوز کنترل کنید. AppShell.xaml.cs را به موارد زیر به روز کنید:

namespace OAuthSample;

public partial class App : Application
{
public App()
{
InitializeComponent();
}

protected override Window CreateWindow(IActivationState? activationState)
{
var displayInfo = DeviceDisplay.Current.MainDisplayInfo;
var width = 700;
var height = 500;
var centerX = (displayInfo.Width / displayInfo.Density – width) / 2;
var centerY = (displayInfo.Height / displayInfo.Density – height) / 2;

return new Window(new AppShell())
{
Width = width,
Height = height,
X = centerX,
Y = centerY
};
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

روی ویندوز تست کنید

با نمایه ماشین ویندوز اجرا شود

حساب کاربری را در مرورگر انتخاب کنید توجه: این حساب باید به عنوان یک کاربر آزمایشی در بخش آزمایش کاربر صفحه رضایت OAuth مشخص شود (این محدودیت پس از انتشار برنامه شما برداشته خواهد شد).

تست روی اندروید

با نمایه شبیه ساز اندروید اجرا کنید

حساب کاربری را در مرورگر انتخاب کنید توجه: این حساب باید به عنوان یک کاربر آزمایشی در بخش آزمایش کاربر صفحه رضایت OAuth مشخص شود (این محدودیت پس از انتشار برنامه شما برداشته خواهد شد).

برنامه نمونه Github

https://github.com/adiamante/maui.oauth.sample

مراجع

اسناد هویت گوگل

نحوه انجام: احراز هویت OAuth2.0 در NET MAUI با استفاده از ارائه دهندگان ابر شخصی

#7. OAuth 2.0 | آپلود فایل در Google Drive توسط API با استفاده از Postman | ساده | آپلود فایل تا 5 مگابایت |

Google Cloud Console

  • اگر پروژه هنوز وجود ندارد ایجاد کنید
  • پروژه خود را انتخاب کنید

انتخاب / پروژه جدید

پیوند API ها و خدمات

پیوند APIs & Services را فعال کنید

جستجوی API گوگل درایو

  • صفحه رضایت OAuth را تنظیم کنید

پیوند صفحه رضایت OAuth

  • مطمئن شوید که کاربر آزمایشی خود را اضافه کنید

کاربر آزمایشی را اضافه کنید

ایجاد پیوند اعتبار

  • Windows OAuth Client ID
    • Universal Windows Platform (UWP) را انتخاب کنید
    • شناسه فروشگاه را برای آزمایش تنظیم کنید. برای یک برنامه واقعی باید متفاوت باشد

Windows OAuth Client ID

  • شناسه مشتری OAuth Android
    • اندروید را انتخاب کنید
    • نام بسته را مانند شناسه برنامه پروژه تنظیم کنید
    • اثر انگشت گواهی SHA-1 را روی تنظیم کنید 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00. برای یک اپلیکیشن واقعی باید خودتان آن را بسازید. برای ویندوز، می توانید جاوا را نصب کنید که دارای ابزار کلیدی در پوشه bin است که می توانید از آن استفاده کنید.
    • طرح URI سفارشی فعال شد

Android OAuth Client ID

پروژه NET MAUI

از یک پروژه جدید شروع کنید

بسته های NuGet زیر را نصب کنید

  • Google.Apis.Auth
  • Google.Apis.Drive.v3
  • Google.Apis.Oauth2.v2

اندروید را راه اندازی کنید

فایل WebAuthenticatorCallbackActivity.cs را به پوشه Platform/Android با محتوای زیر اضافه کنید:

using Android.App;
using Android.Content;
using Android.Content.PM;

namespace OAuthSample.Platforms.Android;

[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
[IntentFilter(new[] { Intent.ActionView },
              Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
              DataScheme = CALLBACK_SCHEME)]
public class WebAuthenticationCallbackActivity : Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity
{

    const string CALLBACK_SCHEME = "com.companyname.oauthsample";
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سرویس GoogleDrive را راه اندازی کنید

فایل GoogleDriveService.cs را با محتوای زیر به پوشه Services اضافه کنید (شناسه سرویس گیرنده UWP و اندروید خود را در بالا تنظیم کنید):

using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Oauth2.v2;
using Google.Apis.Services;
using System.Diagnostics;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Nodes;

namespace OAuthSample.Services;

public class GoogleDriveService
{
    readonly string _windowsClientId = "__UWP_CLIENT_ID_HERE__";      // UWP client
    readonly string _androidClientId = "__ANDROID_CLIENT_ID_HERE__";  // Android client

    Oauth2Service? _oauth2Service;
    DriveService? _driveService;
    GoogleCredential? _credential;
    string? _email;

    public bool IsSignedIn => _credential != null;
    public string? Email => _email;

    public async Task Init()
    {
        var hasRefreshToken = await SecureStorage.GetAsync("refresh_token") is not null;
        if (!IsSignedIn && hasRefreshToken)
        {
            await SignIn();
        }
    }

    public async Task SignIn()
    {
        var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        var expiresIn = Preferences.Get("access_token_epires_in", 0L);
        var isExpired = now - 10 > expiresIn;   // 10 second buffer
        var hasRefreshToken = await SecureStorage.GetAsync("refresh_token") is not null;

        if (isExpired && hasRefreshToken)
        {
            Debug.WriteLine("Using refresh token");
            await RefreshToken();
        }
        else if (isExpired)     // No refresh token
        {
            Debug.WriteLine("Starting auth code flow");
            if (DeviceInfo.Current.Platform == DevicePlatform.WinUI)
            {
                await DoAuthCodeFlowWindows();
            }
            else if (DeviceInfo.Current.Platform == DevicePlatform.Android)
            {
                await DoAuthCodeFlowAndroid();
            }
            else
            {
                throw new NotImplementedException($"Auth flow for platform {DeviceInfo.Current.Platform} not implemented.");
            }
        }

        var accesToken = await SecureStorage.GetAsync("access_token");
        _credential = GoogleCredential.FromAccessToken(accesToken);
        _oauth2Service = new Oauth2Service(new BaseClientService.Initializer
        {
            HttpClientInitializer = _credential,
            ApplicationName = "yeetmedia3"
        });
        _driveService = new DriveService(new BaseClientService.Initializer
        {
            HttpClientInitializer = _credential,
            ApplicationName = "yeetmedia3"
        });
        var userInfo = await _oauth2Service.Userinfo.Get().ExecuteAsync();
        _email = userInfo.Email;
    }

    public async Task ListFiles()
    {
        var request = _driveService!.Files.List();
        var fileList = await request.ExecuteAsync();
        var stringBuilder = new StringBuilder();

        stringBuilder.AppendLine("Files:");
        stringBuilder.AppendLine();
        if (fileList.Files != null && fileList.Files.Count > 0)
        {
            foreach (var file in fileList.Files)
            {
                stringBuilder.AppendLine($"Files: {file.Name} ({file.Id}");
            }
        }
        else
        {
            stringBuilder.AppendLine("No files found.");
        }
        return stringBuilder.ToString();
    }

    public async Task SignOut()
    {
        await RevokeTokens();
    }

    private async Task DoAuthCodeFlowWindows()
    {
        var authUrl = "https://accounts.google.com/o/oauth2/v2/auth";
        var clientId = _windowsClientId;
        var localPort = 12345;
        var redirectUri = $"http://localhost:{localPort}";
        var codeVerifier = GenerateCodeVerifier();
        var codeChallenge = GenerateCodeChallenge(codeVerifier);
        var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);
        var queryString = string.Join("&", parameters.Select(param => $"{param.Key}={param.Value}"));
        var fullAuthUrl = $"{authUrl}?{queryString}";

        await Launcher.OpenAsync(fullAuthUrl);
        var authorizationCode = await StartLocalHttpServerAsync(localPort);

        await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);
    }

    private async Task DoAuthCodeFlowAndroid()
    {
        var authUrl = "https://accounts.google.com/o/oauth2/v2/auth";
        var clientId = _androidClientId;
        var redirectUri = "com.companyname.yeetmedia3://";  // requires a period: https://developers.google.com/identity/protocols/oauth2/native-app#android
        var codeVerifier = GenerateCodeVerifier();
        var codeChallenge = GenerateCodeChallenge(codeVerifier);
        var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);
        var queryString = string.Join("&", parameters.Select(param => $"{param.Key}={param.Value}"));
        var fullAuthUrl = $"{authUrl}?{queryString}";
#pragma warning disable CA1416
        var authCodeResponse = await WebAuthenticator.AuthenticateAsync(new Uri(fullAuthUrl), new Uri("com.companyname.yeetmedia3://"));
#pragma warning restore CA1416
        var authorizationCode = authCodeResponse.Properties["code"];

        await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);
    }

    private static Dictionary GenerateAuthParameters(string redirectUri, string clientId, string codeChallenge)
    {
        return new Dictionary
        {
            //{ "scope", "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.appdata" },
            { "scope", string.Join(' ', [Oauth2Service.Scope.UserinfoProfile, Oauth2Service.Scope.UserinfoEmail, DriveService.Scope.Drive, DriveService.Scope.DriveFile, DriveService.Scope.DriveAppdata]) },
            { "access_type", "offline" },
            { "include_granted_scopes", "true" },
            { "response_type", "code" },
            //{ "state", "state_parameter_passthrough_value" },
            { "redirect_uri", redirectUri },
            { "client_id", clientId },
            { "code_challenge_method", "S256" },
            { "code_challenge", codeChallenge },
            //{ "prompt", "consent" }
        };
    }

    private static async Task GetInitialToken(string authorizationCode, string redirectUri, string clientId, string codeVerifier)
    {
        var tokenEndpoint = "https://oauth2.googleapis.com/token";
        var client = new HttpClient();
        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
        {
            Content = new FormUrlEncodedContent(
            [
                new KeyValuePair("grant_type", "authorization_code"),
                new KeyValuePair("code", authorizationCode),
                new KeyValuePair("redirect_uri", redirectUri),
                new KeyValuePair("client_id", clientId),
                new KeyValuePair("code_verifier", codeVerifier)
            ])
        };

        var response = await client.SendAsync(tokenRequest);
        var responseBody = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode) throw new Exception($"Error requesting token: {responseBody}");

        Debug.WriteLine($"Access token: {responseBody}");
        var jsonToken = JsonObject.Parse(responseBody);
        var accessToken = jsonToken!["access_token"]!.ToString();
        var refreshToken = jsonToken!["refresh_token"]!.ToString();
        var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken!["expires_in"]!.ToString());
        await SecureStorage.SetAsync("access_token", accessToken);
        await SecureStorage.SetAsync("refresh_token", refreshToken);
        Preferences.Set("access_token_epires_in", accessTokenExpiresIn);
    }

    private async Task RefreshToken()
    {
        var clientId = DeviceInfo.Current.Platform == DevicePlatform.WinUI ? _windowsClientId : _androidClientId;
        var tokenEndpoint = "https://oauth2.googleapis.com/token";
        var refreshToken = await SecureStorage.GetAsync("refresh_token");
        var client = new HttpClient();
        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
        {
            Content = new FormUrlEncodedContent(
                [
                    new KeyValuePair("client_id", clientId),
                    new KeyValuePair("grant_type", "refresh_token"),
                    new KeyValuePair("refresh_token", refreshToken!)
                ]
            )
        };

        var response = await client.SendAsync(tokenRequest);
        var responseBody = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode) throw new Exception($"Error requesting token: {responseBody}");

        Debug.WriteLine($"Refresh token: {responseBody}");
        var jsonToken = JsonObject.Parse(responseBody);
        var accessToken = jsonToken!["access_token"]!.ToString();
        var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken!["expires_in"]!.ToString());
        await SecureStorage.SetAsync("access_token", accessToken);
        Preferences.Set("access_token_epires_in", accessTokenExpiresIn);
    }

    private async Task RevokeTokens()
    {
        var revokeEndpoint = "https://oauth2.googleapis.com/revoke";
        var access_token = await SecureStorage.GetAsync("access_token");
        var client = new HttpClient();
        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, revokeEndpoint)
        {
            Content = new FormUrlEncodedContent(
                [
                    new KeyValuePair("token", access_token!),
                ]
            )
        };

        var response = await client.SendAsync(tokenRequest);
        var responseBody = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode) throw new Exception($"Error revoking token: {responseBody}");

        Debug.WriteLine($"Revoke token: {responseBody}");
        SecureStorage.Remove("access_token");
        SecureStorage.Remove("refresh_token");
        Preferences.Remove("access_token_epires_in");

        _credential = null;
        _oauth2Service = null;
        _driveService = null;
    }

    private static async Task StartLocalHttpServerAsync(int port)
    {
        var listener = new HttpListener();
        listener.Prefixes.Add($"http://localhost:{port}/");
        listener.Start();

        Debug.WriteLine($"Listening on http://localhost:{port}/...");
        var context = await listener.GetContextAsync();

        var code = context.Request.QueryString["code"];
        var response = context.Response;
        var responseString = "Authorization complete. You can close this window.";
        var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
        response.ContentLength64 = buffer.Length;
        await response.OutputStream.WriteAsync(buffer);
        response.OutputStream.Close();

        listener.Stop();

        if (code is null) throw new Exception("Auth ode not returned");

        return code;
    }

    private static string GenerateCodeVerifier()
    {
        using var rng = RandomNumberGenerator.Create();
        var bytes = new byte[32]; // Length can vary, e.g., 43-128 characters
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace("https://dev.to/", '_');
    }

    private static string GenerateCodeChallenge(string codeVerifier)
    {
        var hash = SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier));
        return Convert.ToBase64String(hash)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace("https://dev.to/", '_');
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

MainPage.xaml را به موارد زیر به روز کنید:



    
        
            
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

MainPage.xaml.cs را به موارد زیر به روز کنید:

using OAuthSample.Services;

namespace OAuthSample;

public partial class MainPage : ContentPage
{
    readonly GoogleDriveService _googleDriveService = new();
    public MainPage()
    {
        InitializeComponent();
    }

    private async void ContentPage_Loaded(object sender, EventArgs e)
    {
        await _googleDriveService.Init();
        UpdateButton();
    }

    private async void SignIn_Clicked(object sender, EventArgs e)
    {
        if (SignInButton.Text == "Sign In")
        {
            await _googleDriveService.SignIn();
        }
        else
        {
            await _googleDriveService.SignOut();

        }
        UpdateButton();
    }

    private async void List_Clicked(object sender, EventArgs e)
    {
        ListLabel.Text = await _googleDriveService.ListFiles();
    }

    private void UpdateButton()
    {
        if (_googleDriveService.IsSignedIn)
        {
            SignInButton.Text = $"Sign Out ({_googleDriveService.Email})";
            ListButton.IsVisible = true;
        }
        else
        {
            SignInButton.Text = "Sign In";
            ListButton.IsVisible = false;
            ListLabel.Text = String.Empty;
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

(اختیاری) اندازه پنجره را برای ویندوز کنترل کنید. AppShell.xaml.cs را به موارد زیر به روز کنید:

namespace OAuthSample;

public partial class App : Application
{
    public App()
    {
        InitializeComponent();
    }

    protected override Window CreateWindow(IActivationState? activationState)
    {
        var displayInfo = DeviceDisplay.Current.MainDisplayInfo;
        var width = 700;
        var height = 500;
        var centerX = (displayInfo.Width / displayInfo.Density - width) / 2;
        var centerY = (displayInfo.Height / displayInfo.Density - height) / 2;

        return new Window(new AppShell())
        {
            Width = width,
            Height = height,
            X = centerX,
            Y = centerY
        };
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

روی ویندوز تست کنید

  • با نمایه ماشین ویندوز اجرا شود

مشخصات ماشین ویندوز

ورود به سیستم ویندوز

  • حساب کاربری را در مرورگر انتخاب کنید توجه: این حساب باید به عنوان یک کاربر آزمایشی در بخش آزمایش کاربر صفحه رضایت OAuth مشخص شود (این محدودیت پس از انتشار برنامه شما برداشته خواهد شد).

پنجره های حساب را انتخاب کنید

اجازه دسترسی به پنجره ها

احراز هویت کامل ویندوز

فهرست windows را فشار دهید

ویندوز فایل ها را ببینید

تست روی اندروید

  • با نمایه شبیه ساز اندروید اجرا کنید

پروفایل اندروید

ورود به سیستم اندروید

  • حساب کاربری را در مرورگر انتخاب کنید توجه: این حساب باید به عنوان یک کاربر آزمایشی در بخش آزمایش کاربر صفحه رضایت OAuth مشخص شود (این محدودیت پس از انتشار برنامه شما برداشته خواهد شد).

اکانت اندروید را انتخاب کنید

اجازه دسترسی به اندروید

لیست اندروید را فشار دهید

مشاهده فایل های اندروید

برنامه نمونه Github

https://github.com/adiamante/maui.oauth.sample

مراجع

اسناد هویت گوگل

نحوه انجام: احراز هویت OAuth2.0 در NET MAUI با استفاده از ارائه دهندگان ابر شخصی

#7. OAuth 2.0 | آپلود فایل در Google Drive توسط API با استفاده از Postman | ساده | آپلود فایل تا 5 مگابایت |

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا