{"id":89016,"date":"2024-12-20T03:27:51","date_gmt":"2024-12-19T23:57:51","guid":{"rendered":"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/"},"modified":"2024-12-20T03:27:51","modified_gmt":"2024-12-19T23:57:51","slug":"net-maui-google-drive-oauth-on-windows-and-android-4lm4","status":"publish","type":"post","link":"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/","title":{"rendered":"NET MAUI Google Drive OAuth \u062f\u0631 \u0648\u06cc\u0646\u062f\u0648\u0632 \u0648 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f"},"content":{"rendered":"<p>Summarize this content to 400 words in Persian Lang <\/p>\n<p>  Google Cloud Console<\/p>\n<p>\u0627\u06af\u0631 \u067e\u0631\u0648\u0698\u0647 \u0647\u0646\u0648\u0632 \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f \u0627\u06cc\u062c\u0627\u062f \u06a9\u0646\u06cc\u062f<br \/>\n\u067e\u0631\u0648\u0698\u0647 \u062e\u0648\u062f \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f<\/p>\n<p>\u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth \u0631\u0627 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f<\/p>\n<p>\u0645\u0637\u0645\u0626\u0646 \u0634\u0648\u06cc\u062f \u06a9\u0647 \u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u062e\u0648\u062f \u0631\u0627 \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f<\/p>\n<p>Windows OAuth Client ID<\/p>\n<p>Universal Windows Platform (UWP) \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f<br \/>\n\u0634\u0646\u0627\u0633\u0647 \u0641\u0631\u0648\u0634\u06af\u0627\u0647 \u0631\u0627 \u0628\u0631\u0627\u06cc \u0622\u0632\u0645\u0627\u06cc\u0634 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f. \u0628\u0631\u0627\u06cc \u06cc\u06a9 \u0628\u0631\u0646\u0627\u0645\u0647 \u0648\u0627\u0642\u0639\u06cc \u0628\u0627\u06cc\u062f \u0645\u062a\u0641\u0627\u0648\u062a \u0628\u0627\u0634\u062f<\/p>\n<p>\u0634\u0646\u0627\u0633\u0647 \u0645\u0634\u062a\u0631\u06cc OAuth Android<\/p>\n<p>\u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f<br \/>\n\u0646\u0627\u0645 \u0628\u0633\u062a\u0647 \u0631\u0627 \u0645\u0627\u0646\u0646\u062f \u0634\u0646\u0627\u0633\u0647 \u0628\u0631\u0646\u0627\u0645\u0647 \u067e\u0631\u0648\u0698\u0647 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f<br \/>\n\u0627\u062b\u0631 \u0627\u0646\u06af\u0634\u062a \u06af\u0648\u0627\u0647\u06cc SHA-1 \u0631\u0627 \u0631\u0648\u06cc \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00. \u0628\u0631\u0627\u06cc \u06cc\u06a9 \u0627\u067e\u0644\u06cc\u06a9\u06cc\u0634\u0646 \u0648\u0627\u0642\u0639\u06cc \u0628\u0627\u06cc\u062f \u062e\u0648\u062f\u062a\u0627\u0646 \u0622\u0646 \u0631\u0627 \u0628\u0633\u0627\u0632\u06cc\u062f. \u0628\u0631\u0627\u06cc \u0648\u06cc\u0646\u062f\u0648\u0632\u060c \u0645\u06cc \u062a\u0648\u0627\u0646\u06cc\u062f \u062c\u0627\u0648\u0627 \u0631\u0627 \u0646\u0635\u0628 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062f\u0627\u0631\u0627\u06cc \u0627\u0628\u0632\u0627\u0631 \u06a9\u0644\u06cc\u062f\u06cc \u062f\u0631 \u067e\u0648\u0634\u0647 bin \u0627\u0633\u062a \u06a9\u0647 \u0645\u06cc \u062a\u0648\u0627\u0646\u06cc\u062f \u0627\u0632 \u0622\u0646 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u06a9\u0646\u06cc\u062f.<br \/>\n\u0637\u0631\u062d URI \u0633\u0641\u0627\u0631\u0634\u06cc \u0641\u0639\u0627\u0644 \u0634\u062f<\/p>\n<p>  \u067e\u0631\u0648\u0698\u0647 NET MAUI<\/p>\n<p>\u0627\u0632 \u06cc\u06a9 \u067e\u0631\u0648\u0698\u0647 \u062c\u062f\u06cc\u062f \u0634\u0631\u0648\u0639 \u06a9\u0646\u06cc\u062f<\/p>\n<p>  \u0628\u0633\u062a\u0647 \u0647\u0627\u06cc NuGet \u0632\u06cc\u0631 \u0631\u0627 \u0646\u0635\u0628 \u06a9\u0646\u06cc\u062f<\/p>\n<p>Google.Apis.Auth<br \/>\nGoogle.Apis.Drive.v3<br \/>\nGoogle.Apis.Oauth2.v2<\/p>\n<p>  \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0631\u0627 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f<\/p>\n<p>\u0641\u0627\u06cc\u0644 WebAuthenticatorCallbackActivity.cs \u0631\u0627 \u0628\u0647 \u067e\u0648\u0634\u0647 Platform\/Android \u0628\u0627 \u0645\u062d\u062a\u0648\u0627\u06cc \u0632\u06cc\u0631 \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f:<\/p>\n<p>using Android.App;<br \/>\nusing Android.Content;<br \/>\nusing Android.Content.PM;<\/p>\n<p>namespace OAuthSample.Platforms.Android;<\/p>\n[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]\n[IntentFilter(new[] { Intent.ActionView },<br \/>\n              Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },<br \/>\n              DataScheme = CALLBACK_SCHEME)]\npublic class WebAuthenticationCallbackActivity : Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity<br \/>\n{<\/p>\n<p>    const string CALLBACK_SCHEME = &#8220;com.companyname.oauthsample&#8221;;<br \/>\n}<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>  \u0633\u0631\u0648\u06cc\u0633 GoogleDrive \u0631\u0627 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f<\/p>\n<p>\u0641\u0627\u06cc\u0644 GoogleDriveService.cs \u0631\u0627 \u0628\u0627 \u0645\u062d\u062a\u0648\u0627\u06cc \u0632\u06cc\u0631 \u0628\u0647 \u067e\u0648\u0634\u0647 Services \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f (\u0634\u0646\u0627\u0633\u0647 \u0633\u0631\u0648\u06cc\u0633 \u06af\u06cc\u0631\u0646\u062f\u0647 UWP \u0648 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u062e\u0648\u062f \u0631\u0627 \u062f\u0631 \u0628\u0627\u0644\u0627 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f):<\/p>\n<p>using Google.Apis.Auth.OAuth2;<br \/>\nusing Google.Apis.Drive.v3;<br \/>\nusing Google.Apis.Oauth2.v2;<br \/>\nusing Google.Apis.Services;<br \/>\nusing System.Diagnostics;<br \/>\nusing System.Net;<br \/>\nusing System.Security.Cryptography;<br \/>\nusing System.Text;<br \/>\nusing System.Text.Json.Nodes;<\/p>\n<p>namespace OAuthSample.Services;<\/p>\n<p>public class GoogleDriveService<br \/>\n{<br \/>\n    readonly string _windowsClientId = &#8220;__UWP_CLIENT_ID_HERE__&#8221;;      \/\/ UWP client<br \/>\n    readonly string _androidClientId = &#8220;__ANDROID_CLIENT_ID_HERE__&#8221;;  \/\/ Android client<\/p>\n<p>    Oauth2Service? _oauth2Service;<br \/>\n    DriveService? _driveService;<br \/>\n    GoogleCredential? _credential;<br \/>\n    string? _email;<\/p>\n<p>    public bool IsSignedIn =&gt; _credential != null;<br \/>\n    public string? Email =&gt; _email;<\/p>\n<p>    public async Task Init()<br \/>\n    {<br \/>\n        var hasRefreshToken = await SecureStorage.GetAsync(&#8220;refresh_token&#8221;) is not null;<br \/>\n        if (!IsSignedIn &amp;&amp; hasRefreshToken)<br \/>\n        {<br \/>\n            await SignIn();<br \/>\n        }<br \/>\n    }<\/p>\n<p>    public async Task SignIn()<br \/>\n    {<br \/>\n        var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();<br \/>\n        var expiresIn = Preferences.Get(&#8220;access_token_epires_in&#8221;, 0L);<br \/>\n        var isExpired = now &#8211; 10 &gt; expiresIn;   \/\/ 10 second buffer<br \/>\n        var hasRefreshToken = await SecureStorage.GetAsync(&#8220;refresh_token&#8221;) is not null;<\/p>\n<p>        if (isExpired &amp;&amp; hasRefreshToken)<br \/>\n        {<br \/>\n            Debug.WriteLine(&#8220;Using refresh token&#8221;);<br \/>\n            await RefreshToken();<br \/>\n        }<br \/>\n        else if (isExpired)     \/\/ No refresh token<br \/>\n        {<br \/>\n            Debug.WriteLine(&#8220;Starting auth code flow&#8221;);<br \/>\n            if (DeviceInfo.Current.Platform == DevicePlatform.WinUI)<br \/>\n            {<br \/>\n                await DoAuthCodeFlowWindows();<br \/>\n            }<br \/>\n            else if (DeviceInfo.Current.Platform == DevicePlatform.Android)<br \/>\n            {<br \/>\n                await DoAuthCodeFlowAndroid();<br \/>\n            }<br \/>\n            else<br \/>\n            {<br \/>\n                throw new NotImplementedException($&#8221;Auth flow for platform {DeviceInfo.Current.Platform} not implemented.&#8221;);<br \/>\n            }<br \/>\n        }<\/p>\n<p>        var accesToken = await SecureStorage.GetAsync(&#8220;access_token&#8221;);<br \/>\n        _credential = GoogleCredential.FromAccessToken(accesToken);<br \/>\n        _oauth2Service = new Oauth2Service(new BaseClientService.Initializer<br \/>\n        {<br \/>\n            HttpClientInitializer = _credential,<br \/>\n            ApplicationName = &#8220;yeetmedia3&#8221;<br \/>\n        });<br \/>\n        _driveService = new DriveService(new BaseClientService.Initializer<br \/>\n        {<br \/>\n            HttpClientInitializer = _credential,<br \/>\n            ApplicationName = &#8220;yeetmedia3&#8221;<br \/>\n        });<br \/>\n        var userInfo = await _oauth2Service.Userinfo.Get().ExecuteAsync();<br \/>\n        _email = userInfo.Email;<br \/>\n    }<\/p>\n<p>    public async Task ListFiles()<br \/>\n    {<br \/>\n        var request = _driveService!.Files.List();<br \/>\n        var fileList = await request.ExecuteAsync();<br \/>\n        var stringBuilder = new StringBuilder();<\/p>\n<p>        stringBuilder.AppendLine(&#8220;Files:&#8221;);<br \/>\n        stringBuilder.AppendLine();<br \/>\n        if (fileList.Files != null &amp;&amp; fileList.Files.Count &gt; 0)<br \/>\n        {<br \/>\n            foreach (var file in fileList.Files)<br \/>\n            {<br \/>\n                stringBuilder.AppendLine($&#8221;Files: {file.Name} ({file.Id}&#8221;);<br \/>\n            }<br \/>\n        }<br \/>\n        else<br \/>\n        {<br \/>\n            stringBuilder.AppendLine(&#8220;No files found.&#8221;);<br \/>\n        }<br \/>\n        return stringBuilder.ToString();<br \/>\n    }<\/p>\n<p>    public async Task SignOut()<br \/>\n    {<br \/>\n        await RevokeTokens();<br \/>\n    }<\/p>\n<p>    private async Task DoAuthCodeFlowWindows()<br \/>\n    {<br \/>\n        var authUrl = &#8220;https:\/\/accounts.google.com\/o\/oauth2\/v2\/auth&#8221;;<br \/>\n        var clientId = _windowsClientId;<br \/>\n        var localPort = 12345;<br \/>\n        var redirectUri = $&#8221;http:\/\/localhost:{localPort}&#8221;;<br \/>\n        var codeVerifier = GenerateCodeVerifier();<br \/>\n        var codeChallenge = GenerateCodeChallenge(codeVerifier);<br \/>\n        var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);<br \/>\n        var queryString = string.Join(&#8220;&amp;&#8221;, parameters.Select(param =&gt; $&#8221;{param.Key}={param.Value}&#8221;));<br \/>\n        var fullAuthUrl = $&#8221;{authUrl}?{queryString}&#8221;;<\/p>\n<p>        await Launcher.OpenAsync(fullAuthUrl);<br \/>\n        var authorizationCode = await StartLocalHttpServerAsync(localPort);<\/p>\n<p>        await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);<br \/>\n    }<\/p>\n<p>    private async Task DoAuthCodeFlowAndroid()<br \/>\n    {<br \/>\n        var authUrl = &#8220;https:\/\/accounts.google.com\/o\/oauth2\/v2\/auth&#8221;;<br \/>\n        var clientId = _androidClientId;<br \/>\n        var redirectUri = &#8220;com.companyname.yeetmedia3:\/\/&#8221;;  \/\/ requires a period: https:\/\/developers.google.com\/identity\/protocols\/oauth2\/native-app#android<br \/>\n        var codeVerifier = GenerateCodeVerifier();<br \/>\n        var codeChallenge = GenerateCodeChallenge(codeVerifier);<br \/>\n        var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);<br \/>\n        var queryString = string.Join(&#8220;&amp;&#8221;, parameters.Select(param =&gt; $&#8221;{param.Key}={param.Value}&#8221;));<br \/>\n        var fullAuthUrl = $&#8221;{authUrl}?{queryString}&#8221;;<br \/>\n#pragma warning disable CA1416<br \/>\n        var authCodeResponse = await WebAuthenticator.AuthenticateAsync(new Uri(fullAuthUrl), new Uri(&#8220;com.companyname.yeetmedia3:\/\/&#8221;));<br \/>\n#pragma warning restore CA1416<br \/>\n        var authorizationCode = authCodeResponse.Properties[&#8220;code&#8221;];<\/p>\n<p>        await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);<br \/>\n    }<\/p>\n<p>    private static Dictionary GenerateAuthParameters(string redirectUri, string clientId, string codeChallenge)<br \/>\n    {<br \/>\n        return new Dictionary<br \/>\n        {<br \/>\n            \/\/{ &#8220;scope&#8221;, &#8220;https:\/\/www.googleapis.com\/auth\/drive https:\/\/www.googleapis.com\/auth\/drive.file https:\/\/www.googleapis.com\/auth\/drive.appdata&#8221; },<br \/>\n            { &#8220;scope&#8221;, string.Join(&#8216; &#8216;, [Oauth2Service.Scope.UserinfoProfile, Oauth2Service.Scope.UserinfoEmail, DriveService.Scope.Drive, DriveService.Scope.DriveFile, DriveService.Scope.DriveAppdata]) },<br \/>\n            { &#8220;access_type&#8221;, &#8220;offline&#8221; },<br \/>\n            { &#8220;include_granted_scopes&#8221;, &#8220;true&#8221; },<br \/>\n            { &#8220;response_type&#8221;, &#8220;code&#8221; },<br \/>\n            \/\/{ &#8220;state&#8221;, &#8220;state_parameter_passthrough_value&#8221; },<br \/>\n            { &#8220;redirect_uri&#8221;, redirectUri },<br \/>\n            { &#8220;client_id&#8221;, clientId },<br \/>\n            { &#8220;code_challenge_method&#8221;, &#8220;S256&#8221; },<br \/>\n            { &#8220;code_challenge&#8221;, codeChallenge },<br \/>\n            \/\/{ &#8220;prompt&#8221;, &#8220;consent&#8221; }<br \/>\n        };<br \/>\n    }<\/p>\n<p>    private static async Task GetInitialToken(string authorizationCode, string redirectUri, string clientId, string codeVerifier)<br \/>\n    {<br \/>\n        var tokenEndpoint = &#8220;https:\/\/oauth2.googleapis.com\/token&#8221;;<br \/>\n        var client = new HttpClient();<br \/>\n        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)<br \/>\n        {<br \/>\n            Content = new FormUrlEncodedContent(<br \/>\n            [<br \/>\n                new KeyValuePair(&#8220;grant_type&#8221;, &#8220;authorization_code&#8221;),<br \/>\n                new KeyValuePair(&#8220;code&#8221;, authorizationCode),<br \/>\n                new KeyValuePair(&#8220;redirect_uri&#8221;, redirectUri),<br \/>\n                new KeyValuePair(&#8220;client_id&#8221;, clientId),<br \/>\n                new KeyValuePair(&#8220;code_verifier&#8221;, codeVerifier)<br \/>\n            ])<br \/>\n        };<\/p>\n<p>        var response = await client.SendAsync(tokenRequest);<br \/>\n        var responseBody = await response.Content.ReadAsStringAsync();<\/p>\n<p>        if (!response.IsSuccessStatusCode) throw new Exception($&#8221;Error requesting token: {responseBody}&#8221;);<\/p>\n<p>        Debug.WriteLine($&#8221;Access token: {responseBody}&#8221;);<br \/>\n        var jsonToken = JsonObject.Parse(responseBody);<br \/>\n        var accessToken = jsonToken![&#8220;access_token&#8221;]!.ToString();<br \/>\n        var refreshToken = jsonToken![&#8220;refresh_token&#8221;]!.ToString();<br \/>\n        var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken![&#8220;expires_in&#8221;]!.ToString());<br \/>\n        await SecureStorage.SetAsync(&#8220;access_token&#8221;, accessToken);<br \/>\n        await SecureStorage.SetAsync(&#8220;refresh_token&#8221;, refreshToken);<br \/>\n        Preferences.Set(&#8220;access_token_epires_in&#8221;, accessTokenExpiresIn);<br \/>\n    }<\/p>\n<p>    private async Task RefreshToken()<br \/>\n    {<br \/>\n        var clientId = DeviceInfo.Current.Platform == DevicePlatform.WinUI ? _windowsClientId : _androidClientId;<br \/>\n        var tokenEndpoint = &#8220;https:\/\/oauth2.googleapis.com\/token&#8221;;<br \/>\n        var refreshToken = await SecureStorage.GetAsync(&#8220;refresh_token&#8221;);<br \/>\n        var client = new HttpClient();<br \/>\n        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)<br \/>\n        {<br \/>\n            Content = new FormUrlEncodedContent(<br \/>\n                [<br \/>\n                    new KeyValuePair(&#8220;client_id&#8221;, clientId),<br \/>\n                    new KeyValuePair(&#8220;grant_type&#8221;, &#8220;refresh_token&#8221;),<br \/>\n                    new KeyValuePair(&#8220;refresh_token&#8221;, refreshToken!)<br \/>\n                ]\n            )<br \/>\n        };<\/p>\n<p>        var response = await client.SendAsync(tokenRequest);<br \/>\n        var responseBody = await response.Content.ReadAsStringAsync();<\/p>\n<p>        if (!response.IsSuccessStatusCode) throw new Exception($&#8221;Error requesting token: {responseBody}&#8221;);<\/p>\n<p>        Debug.WriteLine($&#8221;Refresh token: {responseBody}&#8221;);<br \/>\n        var jsonToken = JsonObject.Parse(responseBody);<br \/>\n        var accessToken = jsonToken![&#8220;access_token&#8221;]!.ToString();<br \/>\n        var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken![&#8220;expires_in&#8221;]!.ToString());<br \/>\n        await SecureStorage.SetAsync(&#8220;access_token&#8221;, accessToken);<br \/>\n        Preferences.Set(&#8220;access_token_epires_in&#8221;, accessTokenExpiresIn);<br \/>\n    }<\/p>\n<p>    private async Task RevokeTokens()<br \/>\n    {<br \/>\n        var revokeEndpoint = &#8220;https:\/\/oauth2.googleapis.com\/revoke&#8221;;<br \/>\n        var access_token = await SecureStorage.GetAsync(&#8220;access_token&#8221;);<br \/>\n        var client = new HttpClient();<br \/>\n        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, revokeEndpoint)<br \/>\n        {<br \/>\n            Content = new FormUrlEncodedContent(<br \/>\n                [<br \/>\n                    new KeyValuePair(&#8220;token&#8221;, access_token!),<br \/>\n                ]\n            )<br \/>\n        };<\/p>\n<p>        var response = await client.SendAsync(tokenRequest);<br \/>\n        var responseBody = await response.Content.ReadAsStringAsync();<\/p>\n<p>        if (!response.IsSuccessStatusCode) throw new Exception($&#8221;Error revoking token: {responseBody}&#8221;);<\/p>\n<p>        Debug.WriteLine($&#8221;Revoke token: {responseBody}&#8221;);<br \/>\n        SecureStorage.Remove(&#8220;access_token&#8221;);<br \/>\n        SecureStorage.Remove(&#8220;refresh_token&#8221;);<br \/>\n        Preferences.Remove(&#8220;access_token_epires_in&#8221;);<\/p>\n<p>        _credential = null;<br \/>\n        _oauth2Service = null;<br \/>\n        _driveService = null;<br \/>\n    }<\/p>\n<p>    private static async Task StartLocalHttpServerAsync(int port)<br \/>\n    {<br \/>\n        var listener = new HttpListener();<br \/>\n        listener.Prefixes.Add($&#8221;http:\/\/localhost:{port}\/&#8221;);<br \/>\n        listener.Start();<\/p>\n<p>        Debug.WriteLine($&#8221;Listening on http:\/\/localhost:{port}\/&#8230;&#8221;);<br \/>\n        var context = await listener.GetContextAsync();<\/p>\n<p>        var code = context.Request.QueryString[&#8220;code&#8221;];<br \/>\n        var response = context.Response;<br \/>\n        var responseString = &#8220;Authorization complete. You can close this window.&#8221;;<br \/>\n        var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);<br \/>\n        response.ContentLength64 = buffer.Length;<br \/>\n        await response.OutputStream.WriteAsync(buffer);<br \/>\n        response.OutputStream.Close();<\/p>\n<p>        listener.Stop();<\/p>\n<p>        if (code is null) throw new Exception(&#8220;Auth ode not returned&#8221;);<\/p>\n<p>        return code;<br \/>\n    }<\/p>\n<p>    private static string GenerateCodeVerifier()<br \/>\n    {<br \/>\n        using var rng = RandomNumberGenerator.Create();<br \/>\n        var bytes = new byte[32]; \/\/ Length can vary, e.g., 43-128 characters<br \/>\n        rng.GetBytes(bytes);<br \/>\n        return Convert.ToBase64String(bytes)<br \/>\n            .TrimEnd(&#8216;=&#8217;)<br \/>\n            .Replace(&#8216;+&#8217;, &#8216;-&#8216;)<br \/>\n            .Replace(&#8220;https:\/\/dev.to\/&#8221;, &#8216;_&#8217;);<br \/>\n    }<\/p>\n<p>    private static string GenerateCodeChallenge(string codeVerifier)<br \/>\n    {<br \/>\n        var hash = SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier));<br \/>\n        return Convert.ToBase64String(hash)<br \/>\n            .TrimEnd(&#8216;=&#8217;)<br \/>\n            .Replace(&#8216;+&#8217;, &#8216;-&#8216;)<br \/>\n            .Replace(&#8220;https:\/\/dev.to\/&#8221;, &#8216;_&#8217;);<br \/>\n    }<br \/>\n}<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>MainPage.xaml \u0631\u0627 \u0628\u0647 \u0645\u0648\u0627\u0631\u062f \u0632\u06cc\u0631 \u0628\u0647 \u0631\u0648\u0632 \u06a9\u0646\u06cc\u062f:<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>MainPage.xaml.cs \u0631\u0627 \u0628\u0647 \u0645\u0648\u0627\u0631\u062f \u0632\u06cc\u0631 \u0628\u0647 \u0631\u0648\u0632 \u06a9\u0646\u06cc\u062f:<\/p>\n<p>using OAuthSample.Services;<\/p>\n<p>namespace OAuthSample;<\/p>\n<p>public partial class MainPage : ContentPage<br \/>\n{<br \/>\n    readonly GoogleDriveService _googleDriveService = new();<br \/>\n    public MainPage()<br \/>\n    {<br \/>\n        InitializeComponent();<br \/>\n    }<\/p>\n<p>    private async void ContentPage_Loaded(object sender, EventArgs e)<br \/>\n    {<br \/>\n        await _googleDriveService.Init();<br \/>\n        UpdateButton();<br \/>\n    }<\/p>\n<p>    private async void SignIn_Clicked(object sender, EventArgs e)<br \/>\n    {<br \/>\n        if (SignInButton.Text == &#8220;Sign In&#8221;)<br \/>\n        {<br \/>\n            await _googleDriveService.SignIn();<br \/>\n        }<br \/>\n        else<br \/>\n        {<br \/>\n            await _googleDriveService.SignOut();<\/p>\n<p>        }<br \/>\n        UpdateButton();<br \/>\n    }<\/p>\n<p>    private async void List_Clicked(object sender, EventArgs e)<br \/>\n    {<br \/>\n        ListLabel.Text = await _googleDriveService.ListFiles();<br \/>\n    }<\/p>\n<p>    private void UpdateButton()<br \/>\n    {<br \/>\n        if (_googleDriveService.IsSignedIn)<br \/>\n        {<br \/>\n            SignInButton.Text = $&#8221;Sign Out ({_googleDriveService.Email})&#8221;;<br \/>\n            ListButton.IsVisible = true;<br \/>\n        }<br \/>\n        else<br \/>\n        {<br \/>\n            SignInButton.Text = &#8220;Sign In&#8221;;<br \/>\n            ListButton.IsVisible = false;<br \/>\n            ListLabel.Text = String.Empty;<br \/>\n        }<br \/>\n    }<br \/>\n}<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>(\u0627\u062e\u062a\u06cc\u0627\u0631\u06cc) \u0627\u0646\u062f\u0627\u0632\u0647 \u067e\u0646\u062c\u0631\u0647 \u0631\u0627 \u0628\u0631\u0627\u06cc \u0648\u06cc\u0646\u062f\u0648\u0632 \u06a9\u0646\u062a\u0631\u0644 \u06a9\u0646\u06cc\u062f. AppShell.xaml.cs \u0631\u0627 \u0628\u0647 \u0645\u0648\u0627\u0631\u062f \u0632\u06cc\u0631 \u0628\u0647 \u0631\u0648\u0632 \u06a9\u0646\u06cc\u062f:<\/p>\n<p>namespace OAuthSample;<\/p>\n<p>public partial class App : Application<br \/>\n{<br \/>\n    public App()<br \/>\n    {<br \/>\n        InitializeComponent();<br \/>\n    }<\/p>\n<p>    protected override Window CreateWindow(IActivationState? activationState)<br \/>\n    {<br \/>\n        var displayInfo = DeviceDisplay.Current.MainDisplayInfo;<br \/>\n        var width = 700;<br \/>\n        var height = 500;<br \/>\n        var centerX = (displayInfo.Width \/ displayInfo.Density &#8211; width) \/ 2;<br \/>\n        var centerY = (displayInfo.Height \/ displayInfo.Density &#8211; height) \/ 2;<\/p>\n<p>        return new Window(new AppShell())<br \/>\n        {<br \/>\n            Width = width,<br \/>\n            Height = height,<br \/>\n            X = centerX,<br \/>\n            Y = centerY<br \/>\n        };<br \/>\n    }<br \/>\n}<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>  \u0631\u0648\u06cc \u0648\u06cc\u0646\u062f\u0648\u0632 \u062a\u0633\u062a \u06a9\u0646\u06cc\u062f<\/p>\n<p>\u0628\u0627 \u0646\u0645\u0627\u06cc\u0647 \u0645\u0627\u0634\u06cc\u0646 \u0648\u06cc\u0646\u062f\u0648\u0632 \u0627\u062c\u0631\u0627 \u0634\u0648\u062f<\/p>\n<p>\u062d\u0633\u0627\u0628 \u06a9\u0627\u0631\u0628\u0631\u06cc \u0631\u0627 \u062f\u0631 \u0645\u0631\u0648\u0631\u06af\u0631 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f \u062a\u0648\u062c\u0647: \u0627\u06cc\u0646 \u062d\u0633\u0627\u0628 \u0628\u0627\u06cc\u062f \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u062f\u0631 \u0628\u062e\u0634 \u0622\u0632\u0645\u0627\u06cc\u0634 \u06a9\u0627\u0631\u0628\u0631 \u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth \u0645\u0634\u062e\u0635 \u0634\u0648\u062f (\u0627\u06cc\u0646 \u0645\u062d\u062f\u0648\u062f\u06cc\u062a \u067e\u0633 \u0627\u0632 \u0627\u0646\u062a\u0634\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u0647 \u0634\u0645\u0627 \u0628\u0631\u062f\u0627\u0634\u062a\u0647 \u062e\u0648\u0627\u0647\u062f \u0634\u062f).<\/p>\n<p>  \u062a\u0633\u062a \u0631\u0648\u06cc \u0627\u0646\u062f\u0631\u0648\u06cc\u062f<\/p>\n<p>\u0628\u0627 \u0646\u0645\u0627\u06cc\u0647 \u0634\u0628\u06cc\u0647 \u0633\u0627\u0632 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0627\u062c\u0631\u0627 \u06a9\u0646\u06cc\u062f<\/p>\n<p>\u062d\u0633\u0627\u0628 \u06a9\u0627\u0631\u0628\u0631\u06cc \u0631\u0627 \u062f\u0631 \u0645\u0631\u0648\u0631\u06af\u0631 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f \u062a\u0648\u062c\u0647: \u0627\u06cc\u0646 \u062d\u0633\u0627\u0628 \u0628\u0627\u06cc\u062f \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u062f\u0631 \u0628\u062e\u0634 \u0622\u0632\u0645\u0627\u06cc\u0634 \u06a9\u0627\u0631\u0628\u0631 \u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth \u0645\u0634\u062e\u0635 \u0634\u0648\u062f (\u0627\u06cc\u0646 \u0645\u062d\u062f\u0648\u062f\u06cc\u062a \u067e\u0633 \u0627\u0632 \u0627\u0646\u062a\u0634\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u0647 \u0634\u0645\u0627 \u0628\u0631\u062f\u0627\u0634\u062a\u0647 \u062e\u0648\u0627\u0647\u062f \u0634\u062f).<\/p>\n<p>  \u0628\u0631\u0646\u0627\u0645\u0647 \u0646\u0645\u0648\u0646\u0647 Github<\/p>\n<p>https:\/\/github.com\/adiamante\/maui.oauth.sample<\/p>\n<p>  \u0645\u0631\u0627\u062c\u0639<\/p>\n<p>\u0627\u0633\u0646\u0627\u062f \u0647\u0648\u06cc\u062a \u06af\u0648\u06af\u0644<\/p>\n<p>\u0646\u062d\u0648\u0647 \u0627\u0646\u062c\u0627\u0645: \u0627\u062d\u0631\u0627\u0632 \u0647\u0648\u06cc\u062a OAuth2.0 \u062f\u0631 NET MAUI \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0627\u0631\u0627\u0626\u0647 \u062f\u0647\u0646\u062f\u06af\u0627\u0646 \u0627\u0628\u0631 \u0634\u062e\u0635\u06cc<\/p>\n<p>#7. OAuth 2.0 | \u0622\u067e\u0644\u0648\u062f \u0641\u0627\u06cc\u0644 \u062f\u0631 Google Drive \u062a\u0648\u0633\u0637 API \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 Postman | \u0633\u0627\u062f\u0647 | \u0622\u067e\u0644\u0648\u062f \u0641\u0627\u06cc\u0644 \u062a\u0627 5 \u0645\u06af\u0627\u0628\u0627\u06cc\u062a |<\/p>\n<div data-article-id=\"2165671\" id=\"article-body\" wp_automatic_readability=\"126.70273972603\">\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_84 counter-hierarchy ez-toc-counter-rtl ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">\u0641\u0647\u0631\u0633\u062a \u0645\u0637\u0627\u0644\u0628<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#Google_Cloud_Console\" >Google Cloud Console<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D9%BE%D8%B1%D9%88%DA%98%D9%87_NET_MAUI\" >\u067e\u0631\u0648\u0698\u0647 NET MAUI<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D8%A8%D8%B3%D8%AA%D9%87_%D9%87%D8%A7%DB%8C_NuGet_%D8%B2%DB%8C%D8%B1_%D8%B1%D8%A7_%D9%86%D8%B5%D8%A8_%DA%A9%D9%86%DB%8C%D8%AF\" >\u0628\u0633\u062a\u0647 \u0647\u0627\u06cc NuGet \u0632\u06cc\u0631 \u0631\u0627 \u0646\u0635\u0628 \u06a9\u0646\u06cc\u062f<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF_%D8%B1%D8%A7_%D8%B1%D8%A7%D9%87_%D8%A7%D9%86%D8%AF%D8%A7%D8%B2%DB%8C_%DA%A9%D9%86%DB%8C%D8%AF\" >\u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0631\u0627 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D8%B3%D8%B1%D9%88%DB%8C%D8%B3_GoogleDrive_%D8%B1%D8%A7_%D8%B1%D8%A7%D9%87_%D8%A7%D9%86%D8%AF%D8%A7%D8%B2%DB%8C_%DA%A9%D9%86%DB%8C%D8%AF\" >\u0633\u0631\u0648\u06cc\u0633 GoogleDrive \u0631\u0627 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D8%B1%D9%88%DB%8C_%D9%88%DB%8C%D9%86%D8%AF%D9%88%D8%B2_%D8%AA%D8%B3%D8%AA_%DA%A9%D9%86%DB%8C%D8%AF\" >\u0631\u0648\u06cc \u0648\u06cc\u0646\u062f\u0648\u0632 \u062a\u0633\u062a \u06a9\u0646\u06cc\u062f<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D8%AA%D8%B3%D8%AA_%D8%B1%D9%88%DB%8C_%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF\" >\u062a\u0633\u062a \u0631\u0648\u06cc \u0627\u0646\u062f\u0631\u0648\u06cc\u062f<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87_%D9%86%D9%85%D9%88%D9%86%D9%87_Github\" >\u0628\u0631\u0646\u0627\u0645\u0647 \u0646\u0645\u0648\u0646\u0647 Github<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/nabfollower.com\/blog\/net-maui-google-drive-oauth-on-windows-and-android-4lm4\/#%D9%85%D8%B1%D8%A7%D8%AC%D8%B9\" >\u0645\u0631\u0627\u062c\u0639<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Google_Cloud_Console\"><\/span>\n<p>  Google Cloud Console<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ul>\n<li>\u0627\u06af\u0631 \u067e\u0631\u0648\u0698\u0647 \u0647\u0646\u0648\u0632 \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f \u0627\u06cc\u062c\u0627\u062f \u06a9\u0646\u06cc\u062f<\/li>\n<li>\u067e\u0631\u0648\u0698\u0647 \u062e\u0648\u062f \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f<\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqquop64mq7zofjtl3sb6.png\" alt=\"\u067e\u06cc\u0648\u0646\u062f API \u0647\u0627 \u0648 \u062e\u062f\u0645\u0627\u062a\" loading=\"lazy\" width=\"486\" height=\"270\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20orihczxvou7avt41t7.png\" alt=\"\u067e\u06cc\u0648\u0646\u062f APIs &#038; Services \u0631\u0627 \u0641\u0639\u0627\u0644 \u06a9\u0646\u06cc\u062f\" loading=\"lazy\" width=\"800\" height=\"185\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdx00vyhx3uhaiohmnti2.png\" alt=\"\u062c\u0633\u062a\u062c\u0648\u06cc API \u06af\u0648\u06af\u0644 \u062f\u0631\u0627\u06cc\u0648\" loading=\"lazy\" width=\"800\" height=\"359\" title=\"\"><\/p>\n<ul>\n<li>\u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth \u0631\u0627 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86s7ue68gqgxnuvg4fo0.png\" alt=\"\u067e\u06cc\u0648\u0646\u062f \u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth\" loading=\"lazy\" width=\"517\" height=\"434\" title=\"\"><\/p>\n<ul>\n<li>\u0645\u0637\u0645\u0626\u0646 \u0634\u0648\u06cc\u062f \u06a9\u0647 \u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u062e\u0648\u062f \u0631\u0627 \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf8fumhhdmd0mca6ljrq.png\" alt=\"\u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u0631\u0627 \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f\" loading=\"lazy\" width=\"800\" height=\"804\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgvj0g8cgk7cmhtm5wn7.png\" alt=\"\u0627\u06cc\u062c\u0627\u062f \u067e\u06cc\u0648\u0646\u062f \u0627\u0639\u062a\u0628\u0627\u0631\" loading=\"lazy\" width=\"800\" height=\"294\" title=\"\"><\/p>\n<ul>\n<li>Windows OAuth Client ID\n<ul>\n<li>Universal Windows Platform (UWP) \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f<\/li>\n<li>\u0634\u0646\u0627\u0633\u0647 \u0641\u0631\u0648\u0634\u06af\u0627\u0647 \u0631\u0627 \u0628\u0631\u0627\u06cc \u0622\u0632\u0645\u0627\u06cc\u0634 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f. \u0628\u0631\u0627\u06cc \u06cc\u06a9 \u0628\u0631\u0646\u0627\u0645\u0647 \u0648\u0627\u0642\u0639\u06cc \u0628\u0627\u06cc\u062f \u0645\u062a\u0641\u0627\u0648\u062a \u0628\u0627\u0634\u062f<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54idq72vsyo9xpaa5ndx.png\" alt=\"Windows OAuth Client ID\" loading=\"lazy\" width=\"800\" height=\"516\" title=\"\"><\/p>\n<ul>\n<li>\u0634\u0646\u0627\u0633\u0647 \u0645\u0634\u062a\u0631\u06cc OAuth Android\n<ul>\n<li>\u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f<\/li>\n<li>\u0646\u0627\u0645 \u0628\u0633\u062a\u0647 \u0631\u0627 \u0645\u0627\u0646\u0646\u062f \u0634\u0646\u0627\u0633\u0647 \u0628\u0631\u0646\u0627\u0645\u0647 \u067e\u0631\u0648\u0698\u0647 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f<\/li>\n<li>\u0627\u062b\u0631 \u0627\u0646\u06af\u0634\u062a \u06af\u0648\u0627\u0647\u06cc SHA-1 \u0631\u0627 \u0631\u0648\u06cc \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f <code>00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00<\/code>. \u0628\u0631\u0627\u06cc \u06cc\u06a9 \u0627\u067e\u0644\u06cc\u06a9\u06cc\u0634\u0646 \u0648\u0627\u0642\u0639\u06cc \u0628\u0627\u06cc\u062f \u062e\u0648\u062f\u062a\u0627\u0646 \u0622\u0646 \u0631\u0627 \u0628\u0633\u0627\u0632\u06cc\u062f. \u0628\u0631\u0627\u06cc \u0648\u06cc\u0646\u062f\u0648\u0632\u060c \u0645\u06cc \u062a\u0648\u0627\u0646\u06cc\u062f \u062c\u0627\u0648\u0627 \u0631\u0627 \u0646\u0635\u0628 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062f\u0627\u0631\u0627\u06cc \u0627\u0628\u0632\u0627\u0631 \u06a9\u0644\u06cc\u062f\u06cc \u062f\u0631 \u067e\u0648\u0634\u0647 bin \u0627\u0633\u062a \u06a9\u0647 \u0645\u06cc \u062a\u0648\u0627\u0646\u06cc\u062f \u0627\u0632 \u0622\u0646 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u06a9\u0646\u06cc\u062f.<\/li>\n<li>\u0637\u0631\u062d URI \u0633\u0641\u0627\u0631\u0634\u06cc \u0641\u0639\u0627\u0644 \u0634\u062f<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89wbejzb0o4tpd78fsry.png\" alt=\"Android OAuth Client ID\" loading=\"lazy\" width=\"800\" height=\"462\" title=\"\"><\/p>\n<h2><span class=\"ez-toc-section\" id=\"%D9%BE%D8%B1%D9%88%DA%98%D9%87_NET_MAUI\"><\/span>\n<p>  \u067e\u0631\u0648\u0698\u0647 NET MAUI<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\u0627\u0632 \u06cc\u06a9 \u067e\u0631\u0648\u0698\u0647 \u062c\u062f\u06cc\u062f \u0634\u0631\u0648\u0639 \u06a9\u0646\u06cc\u062f<\/p>\n<h3><span class=\"ez-toc-section\" id=\"%D8%A8%D8%B3%D8%AA%D9%87_%D9%87%D8%A7%DB%8C_NuGet_%D8%B2%DB%8C%D8%B1_%D8%B1%D8%A7_%D9%86%D8%B5%D8%A8_%DA%A9%D9%86%DB%8C%D8%AF\"><\/span>\n<p>  \u0628\u0633\u062a\u0647 \u0647\u0627\u06cc NuGet \u0632\u06cc\u0631 \u0631\u0627 \u0646\u0635\u0628 \u06a9\u0646\u06cc\u062f<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li>Google.Apis.Auth<\/li>\n<li>Google.Apis.Drive.v3<\/li>\n<li>Google.Apis.Oauth2.v2<\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF_%D8%B1%D8%A7_%D8%B1%D8%A7%D9%87_%D8%A7%D9%86%D8%AF%D8%A7%D8%B2%DB%8C_%DA%A9%D9%86%DB%8C%D8%AF\"><\/span>\n<p>  \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0631\u0627 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\u0641\u0627\u06cc\u0644 WebAuthenticatorCallbackActivity.cs \u0631\u0627 \u0628\u0647 \u067e\u0648\u0634\u0647 Platform\/Android \u0628\u0627 \u0645\u062d\u062a\u0648\u0627\u06cc \u0632\u06cc\u0631 \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f:<\/p>\n<div class=\"highlight js-code-highlight\" wp_automatic_readability=\"15\">\n<pre class=\"highlight plaintext\"><code>using Android.App;\nusing Android.Content;\nusing Android.Content.PM;\n\nnamespace OAuthSample.Platforms.Android;\n\n[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]\n[IntentFilter(new[] { Intent.ActionView },\n              Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },\n              DataScheme = CALLBACK_SCHEME)]\npublic class WebAuthenticationCallbackActivity : Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity\n{\n\n    const string CALLBACK_SCHEME = \"com.companyname.oauthsample\";\n}\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"%D8%B3%D8%B1%D9%88%DB%8C%D8%B3_GoogleDrive_%D8%B1%D8%A7_%D8%B1%D8%A7%D9%87_%D8%A7%D9%86%D8%AF%D8%A7%D8%B2%DB%8C_%DA%A9%D9%86%DB%8C%D8%AF\"><\/span>\n<p>  \u0633\u0631\u0648\u06cc\u0633 GoogleDrive \u0631\u0627 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\u0641\u0627\u06cc\u0644 GoogleDriveService.cs \u0631\u0627 \u0628\u0627 \u0645\u062d\u062a\u0648\u0627\u06cc \u0632\u06cc\u0631 \u0628\u0647 \u067e\u0648\u0634\u0647 Services \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f (\u0634\u0646\u0627\u0633\u0647 \u0633\u0631\u0648\u06cc\u0633 \u06af\u06cc\u0631\u0646\u062f\u0647 UWP \u0648 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u062e\u0648\u062f \u0631\u0627 \u062f\u0631 \u0628\u0627\u0644\u0627 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f):<\/p>\n<div class=\"highlight js-code-highlight\" wp_automatic_readability=\"87\">\n<pre class=\"highlight plaintext\"><code>using Google.Apis.Auth.OAuth2;\nusing Google.Apis.Drive.v3;\nusing Google.Apis.Oauth2.v2;\nusing Google.Apis.Services;\nusing System.Diagnostics;\nusing System.Net;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Text.Json.Nodes;\n\nnamespace OAuthSample.Services;\n\npublic class GoogleDriveService\n{\n    readonly string _windowsClientId = \"__UWP_CLIENT_ID_HERE__\";      \/\/ UWP client\n    readonly string _androidClientId = \"__ANDROID_CLIENT_ID_HERE__\";  \/\/ Android client\n\n    Oauth2Service? _oauth2Service;\n    DriveService? _driveService;\n    GoogleCredential? _credential;\n    string? _email;\n\n    public bool IsSignedIn =&gt; _credential != null;\n    public string? Email =&gt; _email;\n\n    public async Task Init()\n    {\n        var hasRefreshToken = await SecureStorage.GetAsync(\"refresh_token\") is not null;\n        if (!IsSignedIn &amp;&amp; hasRefreshToken)\n        {\n            await SignIn();\n        }\n    }\n\n    public async Task SignIn()\n    {\n        var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();\n        var expiresIn = Preferences.Get(\"access_token_epires_in\", 0L);\n        var isExpired = now - 10 &gt; expiresIn;   \/\/ 10 second buffer\n        var hasRefreshToken = await SecureStorage.GetAsync(\"refresh_token\") is not null;\n\n        if (isExpired &amp;&amp; hasRefreshToken)\n        {\n            Debug.WriteLine(\"Using refresh token\");\n            await RefreshToken();\n        }\n        else if (isExpired)     \/\/ No refresh token\n        {\n            Debug.WriteLine(\"Starting auth code flow\");\n            if (DeviceInfo.Current.Platform == DevicePlatform.WinUI)\n            {\n                await DoAuthCodeFlowWindows();\n            }\n            else if (DeviceInfo.Current.Platform == DevicePlatform.Android)\n            {\n                await DoAuthCodeFlowAndroid();\n            }\n            else\n            {\n                throw new NotImplementedException($\"Auth flow for platform {DeviceInfo.Current.Platform} not implemented.\");\n            }\n        }\n\n        var accesToken = await SecureStorage.GetAsync(\"access_token\");\n        _credential = GoogleCredential.FromAccessToken(accesToken);\n        _oauth2Service = new Oauth2Service(new BaseClientService.Initializer\n        {\n            HttpClientInitializer = _credential,\n            ApplicationName = \"yeetmedia3\"\n        });\n        _driveService = new DriveService(new BaseClientService.Initializer\n        {\n            HttpClientInitializer = _credential,\n            ApplicationName = \"yeetmedia3\"\n        });\n        var userInfo = await _oauth2Service.Userinfo.Get().ExecuteAsync();\n        _email = userInfo.Email;\n    }\n\n    public async Task<string> ListFiles()\n    {\n        var request = _driveService!.Files.List();\n        var fileList = await request.ExecuteAsync();\n        var stringBuilder = new StringBuilder();\n\n        stringBuilder.AppendLine(\"Files:\");\n        stringBuilder.AppendLine();\n        if (fileList.Files != null &amp;&amp; fileList.Files.Count &gt; 0)\n        {\n            foreach (var file in fileList.Files)\n            {\n                stringBuilder.AppendLine($\"Files: {file.Name} ({file.Id}\");\n            }\n        }\n        else\n        {\n            stringBuilder.AppendLine(\"No files found.\");\n        }\n        return stringBuilder.ToString();\n    }\n\n    public async Task SignOut()\n    {\n        await RevokeTokens();\n    }\n\n    private async Task DoAuthCodeFlowWindows()\n    {\n        var authUrl = \"https:\/\/accounts.google.com\/o\/oauth2\/v2\/auth\";\n        var clientId = _windowsClientId;\n        var localPort = 12345;\n        var redirectUri = $\"http:\/\/localhost:{localPort}\";\n        var codeVerifier = GenerateCodeVerifier();\n        var codeChallenge = GenerateCodeChallenge(codeVerifier);\n        var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);\n        var queryString = string.Join(\"&amp;\", parameters.Select(param =&gt; $\"{param.Key}={param.Value}\"));\n        var fullAuthUrl = $\"{authUrl}?{queryString}\";\n\n        await Launcher.OpenAsync(fullAuthUrl);\n        var authorizationCode = await StartLocalHttpServerAsync(localPort);\n\n        await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);\n    }\n\n    private async Task DoAuthCodeFlowAndroid()\n    {\n        var authUrl = \"https:\/\/accounts.google.com\/o\/oauth2\/v2\/auth\";\n        var clientId = _androidClientId;\n        var redirectUri = \"com.companyname.yeetmedia3:\/\/\";  \/\/ requires a period: https:\/\/developers.google.com\/identity\/protocols\/oauth2\/native-app#android\n        var codeVerifier = GenerateCodeVerifier();\n        var codeChallenge = GenerateCodeChallenge(codeVerifier);\n        var parameters = GenerateAuthParameters(redirectUri, clientId, codeChallenge);\n        var queryString = string.Join(\"&amp;\", parameters.Select(param =&gt; $\"{param.Key}={param.Value}\"));\n        var fullAuthUrl = $\"{authUrl}?{queryString}\";\n#pragma warning disable CA1416\n        var authCodeResponse = await WebAuthenticator.AuthenticateAsync(new Uri(fullAuthUrl), new Uri(\"com.companyname.yeetmedia3:\/\/\"));\n#pragma warning restore CA1416\n        var authorizationCode = authCodeResponse.Properties[\"code\"];\n\n        await GetInitialToken(authorizationCode, redirectUri, clientId, codeVerifier);\n    }\n\n    private static Dictionary<string string=\"\"> GenerateAuthParameters(string redirectUri, string clientId, string codeChallenge)\n    {\n        return new Dictionary<string string=\"\">\n        {\n            \/\/{ \"scope\", \"https:\/\/www.googleapis.com\/auth\/drive https:\/\/www.googleapis.com\/auth\/drive.file https:\/\/www.googleapis.com\/auth\/drive.appdata\" },\n            { \"scope\", string.Join(' ', [Oauth2Service.Scope.UserinfoProfile, Oauth2Service.Scope.UserinfoEmail, DriveService.Scope.Drive, DriveService.Scope.DriveFile, DriveService.Scope.DriveAppdata]) },\n            { \"access_type\", \"offline\" },\n            { \"include_granted_scopes\", \"true\" },\n            { \"response_type\", \"code\" },\n            \/\/{ \"state\", \"state_parameter_passthrough_value\" },\n            { \"redirect_uri\", redirectUri },\n            { \"client_id\", clientId },\n            { \"code_challenge_method\", \"S256\" },\n            { \"code_challenge\", codeChallenge },\n            \/\/{ \"prompt\", \"consent\" }\n        };\n    }\n\n    private static async Task GetInitialToken(string authorizationCode, string redirectUri, string clientId, string codeVerifier)\n    {\n        var tokenEndpoint = \"https:\/\/oauth2.googleapis.com\/token\";\n        var client = new HttpClient();\n        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)\n        {\n            Content = new FormUrlEncodedContent(\n            [\n                new KeyValuePair<string string=\"\">(\"grant_type\", \"authorization_code\"),\n                new KeyValuePair<string string=\"\">(\"code\", authorizationCode),\n                new KeyValuePair<string string=\"\">(\"redirect_uri\", redirectUri),\n                new KeyValuePair<string string=\"\">(\"client_id\", clientId),\n                new KeyValuePair<string string=\"\">(\"code_verifier\", codeVerifier)\n            ])\n        };\n\n        var response = await client.SendAsync(tokenRequest);\n        var responseBody = await response.Content.ReadAsStringAsync();\n\n        if (!response.IsSuccessStatusCode) throw new Exception($\"Error requesting token: {responseBody}\");\n\n        Debug.WriteLine($\"Access token: {responseBody}\");\n        var jsonToken = JsonObject.Parse(responseBody);\n        var accessToken = jsonToken![\"access_token\"]!.ToString();\n        var refreshToken = jsonToken![\"refresh_token\"]!.ToString();\n        var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken![\"expires_in\"]!.ToString());\n        await SecureStorage.SetAsync(\"access_token\", accessToken);\n        await SecureStorage.SetAsync(\"refresh_token\", refreshToken);\n        Preferences.Set(\"access_token_epires_in\", accessTokenExpiresIn);\n    }\n\n    private async Task RefreshToken()\n    {\n        var clientId = DeviceInfo.Current.Platform == DevicePlatform.WinUI ? _windowsClientId : _androidClientId;\n        var tokenEndpoint = \"https:\/\/oauth2.googleapis.com\/token\";\n        var refreshToken = await SecureStorage.GetAsync(\"refresh_token\");\n        var client = new HttpClient();\n        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)\n        {\n            Content = new FormUrlEncodedContent(\n                [\n                    new KeyValuePair<string string=\"\">(\"client_id\", clientId),\n                    new KeyValuePair<string string=\"\">(\"grant_type\", \"refresh_token\"),\n                    new KeyValuePair<string string=\"\">(\"refresh_token\", refreshToken!)\n                ]\n            )\n        };\n\n        var response = await client.SendAsync(tokenRequest);\n        var responseBody = await response.Content.ReadAsStringAsync();\n\n        if (!response.IsSuccessStatusCode) throw new Exception($\"Error requesting token: {responseBody}\");\n\n        Debug.WriteLine($\"Refresh token: {responseBody}\");\n        var jsonToken = JsonObject.Parse(responseBody);\n        var accessToken = jsonToken![\"access_token\"]!.ToString();\n        var accessTokenExpiresIn = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + int.Parse(jsonToken![\"expires_in\"]!.ToString());\n        await SecureStorage.SetAsync(\"access_token\", accessToken);\n        Preferences.Set(\"access_token_epires_in\", accessTokenExpiresIn);\n    }\n\n    private async Task RevokeTokens()\n    {\n        var revokeEndpoint = \"https:\/\/oauth2.googleapis.com\/revoke\";\n        var access_token = await SecureStorage.GetAsync(\"access_token\");\n        var client = new HttpClient();\n        var tokenRequest = new HttpRequestMessage(HttpMethod.Post, revokeEndpoint)\n        {\n            Content = new FormUrlEncodedContent(\n                [\n                    new KeyValuePair<string string=\"\">(\"token\", access_token!),\n                ]\n            )\n        };\n\n        var response = await client.SendAsync(tokenRequest);\n        var responseBody = await response.Content.ReadAsStringAsync();\n\n        if (!response.IsSuccessStatusCode) throw new Exception($\"Error revoking token: {responseBody}\");\n\n        Debug.WriteLine($\"Revoke token: {responseBody}\");\n        SecureStorage.Remove(\"access_token\");\n        SecureStorage.Remove(\"refresh_token\");\n        Preferences.Remove(\"access_token_epires_in\");\n\n        _credential = null;\n        _oauth2Service = null;\n        _driveService = null;\n    }\n\n    private static async Task<string> StartLocalHttpServerAsync(int port)\n    {\n        var listener = new HttpListener();\n        listener.Prefixes.Add($\"http:\/\/localhost:{port}\/\");\n        listener.Start();\n\n        Debug.WriteLine($\"Listening on http:\/\/localhost:{port}\/...\");\n        var context = await listener.GetContextAsync();\n\n        var code = context.Request.QueryString[\"code\"];\n        var response = context.Response;\n        var responseString = \"Authorization complete. You can close this window.\";\n        var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);\n        response.ContentLength64 = buffer.Length;\n        await response.OutputStream.WriteAsync(buffer);\n        response.OutputStream.Close();\n\n        listener.Stop();\n\n        if (code is null) throw new Exception(\"Auth ode not returned\");\n\n        return code;\n    }\n\n    private static string GenerateCodeVerifier()\n    {\n        using var rng = RandomNumberGenerator.Create();\n        var bytes = new byte[32]; \/\/ Length can vary, e.g., 43-128 characters\n        rng.GetBytes(bytes);\n        return Convert.ToBase64String(bytes)\n            .TrimEnd('=')\n            .Replace('+', '-')\n            .Replace(\"https:\/\/dev.to\/\", '_');\n    }\n\n    private static string GenerateCodeChallenge(string codeVerifier)\n    {\n        var hash = SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier));\n        return Convert.ToBase64String(hash)\n            .TrimEnd('=')\n            .Replace('+', '-')\n            .Replace(\"https:\/\/dev.to\/\", '_');\n    }\n}\n<\/string><\/string><\/string><\/string><\/string><\/string><\/string><\/string><\/string><\/string><\/string><\/string><\/string><\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>MainPage.xaml \u0631\u0627 \u0628\u0647 \u0645\u0648\u0627\u0631\u062f \u0632\u06cc\u0631 \u0628\u0647 \u0631\u0648\u0632 \u06a9\u0646\u06cc\u062f:<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code><?xml version=\"1.0\" encoding=\"utf-8\" ????>\n<contentpage xmlns=\"http:\/\/schemas.microsoft.com\/dotnet\/2021\/maui\" xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2009\/xaml\" x:class=\"OAuthSample.MainPage\" loaded=\"ContentPage_Loaded\">\n    <scrollview>\n        <verticalstacklayout padding=\"30,0\" spacing=\"25\">\n            <button x:name=\"SignInButton\" text=\"Sign In\" clicked=\"SignIn_Clicked\" horizontaloptions=\"Fill\"\/>\n            <button x:name=\"ListButton\" text=\"List\" clicked=\"List_Clicked\" horizontaloptions=\"Fill\" isvisible=\"False\"\/>\n            <label x:name=\"ListLabel\"\/>\n        <\/verticalstacklayout>\n    <\/scrollview>\n<\/contentpage>\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>MainPage.xaml.cs \u0631\u0627 \u0628\u0647 \u0645\u0648\u0627\u0631\u062f \u0632\u06cc\u0631 \u0628\u0647 \u0631\u0648\u0632 \u06a9\u0646\u06cc\u062f:<\/p>\n<div class=\"highlight js-code-highlight\" wp_automatic_readability=\"13\">\n<pre class=\"highlight plaintext\"><code>using OAuthSample.Services;\n\nnamespace OAuthSample;\n\npublic partial class MainPage : ContentPage\n{\n    readonly GoogleDriveService _googleDriveService = new();\n    public MainPage()\n    {\n        InitializeComponent();\n    }\n\n    private async void ContentPage_Loaded(object sender, EventArgs e)\n    {\n        await _googleDriveService.Init();\n        UpdateButton();\n    }\n\n    private async void SignIn_Clicked(object sender, EventArgs e)\n    {\n        if (SignInButton.Text == \"Sign In\")\n        {\n            await _googleDriveService.SignIn();\n        }\n        else\n        {\n            await _googleDriveService.SignOut();\n\n        }\n        UpdateButton();\n    }\n\n    private async void List_Clicked(object sender, EventArgs e)\n    {\n        ListLabel.Text = await _googleDriveService.ListFiles();\n    }\n\n    private void UpdateButton()\n    {\n        if (_googleDriveService.IsSignedIn)\n        {\n            SignInButton.Text = $\"Sign Out ({_googleDriveService.Email})\";\n            ListButton.IsVisible = true;\n        }\n        else\n        {\n            SignInButton.Text = \"Sign In\";\n            ListButton.IsVisible = false;\n            ListLabel.Text = String.Empty;\n        }\n    }\n}\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>(\u0627\u062e\u062a\u06cc\u0627\u0631\u06cc) \u0627\u0646\u062f\u0627\u0632\u0647 \u067e\u0646\u062c\u0631\u0647 \u0631\u0627 \u0628\u0631\u0627\u06cc \u0648\u06cc\u0646\u062f\u0648\u0632 \u06a9\u0646\u062a\u0631\u0644 \u06a9\u0646\u06cc\u062f. AppShell.xaml.cs \u0631\u0627 \u0628\u0647 \u0645\u0648\u0627\u0631\u062f \u0632\u06cc\u0631 \u0628\u0647 \u0631\u0648\u0632 \u06a9\u0646\u06cc\u062f:<\/p>\n<div class=\"highlight js-code-highlight\" wp_automatic_readability=\"13\">\n<pre class=\"highlight plaintext\"><code>namespace OAuthSample;\n\npublic partial class App : Application\n{\n    public App()\n    {\n        InitializeComponent();\n    }\n\n    protected override Window CreateWindow(IActivationState? activationState)\n    {\n        var displayInfo = DeviceDisplay.Current.MainDisplayInfo;\n        var width = 700;\n        var height = 500;\n        var centerX = (displayInfo.Width \/ displayInfo.Density - width) \/ 2;\n        var centerY = (displayInfo.Height \/ displayInfo.Density - height) \/ 2;\n\n        return new Window(new AppShell())\n        {\n            Width = width,\n            Height = height,\n            X = centerX,\n            Y = centerY\n        };\n    }\n}\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"%D8%B1%D9%88%DB%8C_%D9%88%DB%8C%D9%86%D8%AF%D9%88%D8%B2_%D8%AA%D8%B3%D8%AA_%DA%A9%D9%86%DB%8C%D8%AF\"><\/span>\n<p>  \u0631\u0648\u06cc \u0648\u06cc\u0646\u062f\u0648\u0632 \u062a\u0633\u062a \u06a9\u0646\u06cc\u062f<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li>\u0628\u0627 \u0646\u0645\u0627\u06cc\u0647 \u0645\u0627\u0634\u06cc\u0646 \u0648\u06cc\u0646\u062f\u0648\u0632 \u0627\u062c\u0631\u0627 \u0634\u0648\u062f<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1g4fxk6wp4r3x3sgmiv.png\" alt=\"\u0645\u0634\u062e\u0635\u0627\u062a \u0645\u0627\u0634\u06cc\u0646 \u0648\u06cc\u0646\u062f\u0648\u0632\" loading=\"lazy\" width=\"680\" height=\"382\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dpxyxbm0vqo9mh498sx.png\" alt=\"\u0648\u0631\u0648\u062f \u0628\u0647 \u0633\u06cc\u0633\u062a\u0645 \u0648\u06cc\u0646\u062f\u0648\u0632\" loading=\"lazy\" width=\"687\" height=\"491\" title=\"\"><\/p>\n<ul>\n<li>\u062d\u0633\u0627\u0628 \u06a9\u0627\u0631\u0628\u0631\u06cc \u0631\u0627 \u062f\u0631 \u0645\u0631\u0648\u0631\u06af\u0631 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f \u062a\u0648\u062c\u0647: \u0627\u06cc\u0646 \u062d\u0633\u0627\u0628 \u0628\u0627\u06cc\u062f \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u062f\u0631 \u0628\u062e\u0634 \u0622\u0632\u0645\u0627\u06cc\u0634 \u06a9\u0627\u0631\u0628\u0631 \u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth \u0645\u0634\u062e\u0635 \u0634\u0648\u062f (\u0627\u06cc\u0646 \u0645\u062d\u062f\u0648\u062f\u06cc\u062a \u067e\u0633 \u0627\u0632 \u0627\u0646\u062a\u0634\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u0647 \u0634\u0645\u0627 \u0628\u0631\u062f\u0627\u0634\u062a\u0647 \u062e\u0648\u0627\u0647\u062f \u0634\u062f).<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd0glscjjwyetqbla8br6.png\" alt=\"\u067e\u0646\u062c\u0631\u0647 \u0647\u0627\u06cc \u062d\u0633\u0627\u0628 \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f\" loading=\"lazy\" width=\"800\" height=\"714\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx6mx7wnyjsgxij7m9e3.png\" alt=\"\u0627\u062c\u0627\u0632\u0647 \u062f\u0633\u062a\u0631\u0633\u06cc \u0628\u0647 \u067e\u0646\u062c\u0631\u0647 \u0647\u0627\" loading=\"lazy\" width=\"800\" height=\"793\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdge5cjtskevxbgitvpdv.png\" alt=\"\u0627\u062d\u0631\u0627\u0632 \u0647\u0648\u06cc\u062a \u06a9\u0627\u0645\u0644 \u0648\u06cc\u0646\u062f\u0648\u0632\" loading=\"lazy\" width=\"639\" height=\"258\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff56x94h2n8ha2pd1bpvl.png\" alt=\"\u0641\u0647\u0631\u0633\u062a windows \u0631\u0627 \u0641\u0634\u0627\u0631 \u062f\u0647\u06cc\u062f\" loading=\"lazy\" width=\"687\" height=\"496\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp54huc6zef1nos34wxgl.png\" alt=\"\u0648\u06cc\u0646\u062f\u0648\u0632 \u0641\u0627\u06cc\u0644 \u0647\u0627 \u0631\u0627 \u0628\u0628\u06cc\u0646\u06cc\u062f\" loading=\"lazy\" width=\"688\" height=\"493\" title=\"\"><\/p>\n<h3><span class=\"ez-toc-section\" id=\"%D8%AA%D8%B3%D8%AA_%D8%B1%D9%88%DB%8C_%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF\"><\/span>\n<p>  \u062a\u0633\u062a \u0631\u0648\u06cc \u0627\u0646\u062f\u0631\u0648\u06cc\u062f<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li>\u0628\u0627 \u0646\u0645\u0627\u06cc\u0647 \u0634\u0628\u06cc\u0647 \u0633\u0627\u0632 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0627\u062c\u0631\u0627 \u06a9\u0646\u06cc\u062f<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63kuur1c1wkj8ufn9him.png\" alt=\"\u067e\u0631\u0648\u0641\u0627\u06cc\u0644 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f\" loading=\"lazy\" width=\"760\" height=\"183\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1keuq6sf47mdlzp4dcz.png\" alt=\"\u0648\u0631\u0648\u062f \u0628\u0647 \u0633\u06cc\u0633\u062a\u0645 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f\" loading=\"lazy\" width=\"394\" height=\"882\" title=\"\"><\/p>\n<ul>\n<li>\u062d\u0633\u0627\u0628 \u06a9\u0627\u0631\u0628\u0631\u06cc \u0631\u0627 \u062f\u0631 \u0645\u0631\u0648\u0631\u06af\u0631 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f \u062a\u0648\u062c\u0647: \u0627\u06cc\u0646 \u062d\u0633\u0627\u0628 \u0628\u0627\u06cc\u062f \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u062f\u0631 \u0628\u062e\u0634 \u0622\u0632\u0645\u0627\u06cc\u0634 \u06a9\u0627\u0631\u0628\u0631 \u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth \u0645\u0634\u062e\u0635 \u0634\u0648\u062f (\u0627\u06cc\u0646 \u0645\u062d\u062f\u0648\u062f\u06cc\u062a \u067e\u0633 \u0627\u0632 \u0627\u0646\u062a\u0634\u0627\u0631 \u0628\u0631\u0646\u0627\u0645\u0647 \u0634\u0645\u0627 \u0628\u0631\u062f\u0627\u0634\u062a\u0647 \u062e\u0648\u0627\u0647\u062f \u0634\u062f).<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqh6pdz1iu0gxngf6rn7.png\" alt=\"\u0627\u06a9\u0627\u0646\u062a \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f\" loading=\"lazy\" width=\"394\" height=\"882\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx43cb556v3oa15auamon.png\" alt=\"\u0627\u062c\u0627\u0632\u0647 \u062f\u0633\u062a\u0631\u0633\u06cc \u0628\u0647 \u0627\u0646\u062f\u0631\u0648\u06cc\u062f\" loading=\"lazy\" width=\"394\" height=\"882\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9udg02w6brs8zktibhl.png\" alt=\"\u0644\u06cc\u0633\u062a \u0627\u0646\u062f\u0631\u0648\u06cc\u062f \u0631\u0627 \u0641\u0634\u0627\u0631 \u062f\u0647\u06cc\u062f\" loading=\"lazy\" width=\"394\" height=\"882\" title=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7rso0l2er00uow6ocgl.png\" alt=\"\u0645\u0634\u0627\u0647\u062f\u0647 \u0641\u0627\u06cc\u0644 \u0647\u0627\u06cc \u0627\u0646\u062f\u0631\u0648\u06cc\u062f\" loading=\"lazy\" width=\"394\" height=\"882\" title=\"\"><\/p>\n<h2><span class=\"ez-toc-section\" id=\"%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87_%D9%86%D9%85%D9%88%D9%86%D9%87_Github\"><\/span>\n<p>  \u0628\u0631\u0646\u0627\u0645\u0647 \u0646\u0645\u0648\u0646\u0647 Github<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>https:\/\/github.com\/adiamante\/maui.oauth.sample<\/p>\n<h2><span class=\"ez-toc-section\" id=\"%D9%85%D8%B1%D8%A7%D8%AC%D8%B9\"><\/span>\n<p>  \u0645\u0631\u0627\u062c\u0639<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\u0627\u0633\u0646\u0627\u062f \u0647\u0648\u06cc\u062a \u06af\u0648\u06af\u0644<\/p>\n<p>\u0646\u062d\u0648\u0647 \u0627\u0646\u062c\u0627\u0645: \u0627\u062d\u0631\u0627\u0632 \u0647\u0648\u06cc\u062a OAuth2.0 \u062f\u0631 NET MAUI \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0627\u0631\u0627\u0626\u0647 \u062f\u0647\u0646\u062f\u06af\u0627\u0646 \u0627\u0628\u0631 \u0634\u062e\u0635\u06cc<\/p>\n<p>#7. OAuth 2.0 | \u0622\u067e\u0644\u0648\u062f \u0641\u0627\u06cc\u0644 \u062f\u0631 Google Drive \u062a\u0648\u0633\u0637 API \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 Postman | \u0633\u0627\u062f\u0647 | \u0622\u067e\u0644\u0648\u062f \u0641\u0627\u06cc\u0644 \u062a\u0627 5 \u0645\u06af\u0627\u0628\u0627\u06cc\u062a |<\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Summarize this content to 400 words in Persian Lang Google Cloud Console \u0627\u06af\u0631 \u067e\u0631\u0648\u0698\u0647 \u0647\u0646\u0648\u0632 \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f \u0627\u06cc\u062c\u0627\u062f \u06a9\u0646\u06cc\u062f \u067e\u0631\u0648\u0698\u0647 \u062e\u0648\u062f \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f \u0635\u0641\u062d\u0647 \u0631\u0636\u0627\u06cc\u062a OAuth \u0631\u0627 \u062a\u0646\u0638\u06cc\u0645 \u06a9\u0646\u06cc\u062f \u0645\u0637\u0645\u0626\u0646 \u0634\u0648\u06cc\u062f \u06a9\u0647 \u06a9\u0627\u0631\u0628\u0631 \u0622\u0632\u0645\u0627\u06cc\u0634\u06cc \u062e\u0648\u062f \u0631\u0627 \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f Windows OAuth Client ID Universal Windows Platform (UWP) \u0631\u0627 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f \u0634\u0646\u0627\u0633\u0647 \u0641\u0631\u0648\u0634\u06af\u0627\u0647 \u0631\u0627 \u0628\u0631\u0627\u06cc \u0622\u0632\u0645\u0627\u06cc\u0634 &hellip;<\/p>\n","protected":false},"author":2,"featured_media":89018,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"","fifu_image_alt":"","footnotes":""},"categories":[339],"tags":[],"class_list":["post-89016","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dev"],"_links":{"self":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/89016","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/comments?post=89016"}],"version-history":[{"count":0,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/89016\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media\/89018"}],"wp:attachment":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media?parent=89016"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/categories?post=89016"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/tags?post=89016"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}