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
- اگر پروژه هنوز وجود ندارد ایجاد کنید
- پروژه خود را انتخاب کنید
- صفحه رضایت 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 مگابایت |