Bir Delphi uygulamasında kullanıcıyı OAuth2 ve PKCE ile oturum açtırın

Tek bileşen, tek izin türü, tek tarayıcı devri. Bu sayfa sizi boş bir formdan, canlı bir erişim token'ına sahip oturum açmış bir kullanıcıya götürür; her sağlayıcının artık yerel bir masaüstü uygulamasından beklediği akış olan PKCE'li Authorization Code akışını (RFC 7636) kullanarak.

TsgcHTTP_OAuth2_Client
Code verifier ve challenge sizin için üretilir
Delphi 7'den 13'e, C++ Builder, VCL ve FireMonkey

Bir kullanıcıyı oturum açtırmak için gerekenler

Sağlayıcıyla tek bir görsel olmayan bileşen konuşur. Bir web sunucusuna, gömülü bir tarayıcıya veya bir REST çatısına ihtiyacınız yoktur.

Bileşen

TsgcHTTP_OAuth2_Client, sgcHTTP biriminde tanımlanır ve her demonun yaptığı gibi kod içinde oluşturulur.

İzin türü

OAuth2Options.GrantType := auth2CodePKCE. Bu tek atama PKCE'yi açar.

Sürüm

Standard, Professional, Enterprise ve All-Access. İstemci bir Enterprise özelliği değildir, sunucu öyledir.

Platformlar

Windows, macOS, Linux, iOS ve Android. Bileşen, platformun sunduğu tarayıcıyı açar.

PKCE akışı gerçekte ne yapar

PKCE, bir masaüstü uygulaması sır saklayamadığı için vardır. Sırrın yerine, istemcinin akış başlamadan önce bildiğini kanıtladığı bir değer koyar.

1. Bir code verifier üretin

Yüksek entropili rastgele bir dize. sgcWebSockets platformun CSPRNG kaynağından 32 bayt ister ve bunları Base64URL ile kodlar; bu da RFC 7636'nın istediği 43 karakterlik verifier'ı üretir.

2. Code challenge'ı türetin

Verifier'ın SHA-256 özeti, Base64URL ile kodlanmış. Yetkilendirme isteğinde yolculuk eden şey challenge'dır, dolayısıyla yönlendirmeyi dinleyen biri verifier'ı asla görmez.

3. Tarayıcıyı açın

Bileşen yetkilendirme URL'sini client_id, redirect_uri, scope, state, code_challenge ve code_challenge_method=S256 ile kurar, sonra sistem tarayıcısını başlatır.

4. Kullanıcı oturum açar

Onay tarayıcıda, sağlayıcının kendi alan adında, kullanıcının mevcut oturumu, parola yöneticisi ve iki adımlı doğrulama cihazıyla gerçekleşir. Uygulamanız parolayı hiç görmez.

5. Yönlendirme geri döner

Sağlayıcı, code ve state taşıyarak sizin redirect_uri adresinize yönlendirir. Masaüstünde bu URI bir loopback adresidir ve bileşen zaten onu dinliyordur.

6. Kodu takas edin

Bileşen, kodu özgün code_verifier ile birlikte token uç noktasına POST eder. Sağlayıcı SHA-256 özetini yeniden hesaplayıp karşılaştırır. Eşleşirse bir erişim token'ı alırsınız.

Verifier neden önemli

Bir yetkilendirme kodu, yaşadığı birkaç saniye boyunca hamiline yazılı bir değerdir. Yönlendirmeyi gözleyebilen her şey, aynı özel URI şemasına kayıtlı kötü niyetli bir uygulama, bir proxy, paylaşılan bir günlük, onu çalabilir. PKCE olmadan çalınan bu kod bir token basmaya yeter.

PKCE ile token uç noktası, çağıran taraf başlangıçta gönderilen challenge ile eşleşen SHA-256 özetine sahip verifier'ı da sunmadıkça kodu reddeder. Saldırgan yalnızca özeti gördüğü için çalınan kod değersizdir.

Burada yazmanız gereken bir şey yok. GrantType değerini auth2CodePKCE yapın, bileşen 1, 2, 3, 5 ve 6. adımları sizin için yapsın. Devamı, bunu çalıştıran kod ve gerçekten vermeniz gereken iki karar: yönlendirme URI'si ve yenileme token'ının nerede duracağı.

hat üzerinde
# 1. Browser is sent here (query wrapped for reading)
GET https://provider.com/oauth2/authorize
    ?response_type=code
    &client_id=your-client-id
    &redirect_uri=http://127.0.0.1:52413/
    &scope=openid%20profile
    &state=8F3B1C2A-...-9D4E
    &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
    &code_challenge_method=S256

# 2. Provider redirects back to the loopback listener
GET http://127.0.0.1:52413/?code=4/0Ab_5q...&state=8F3B1C2A-...-9D4E

# 3. Component exchanges the code, adding the verifier
POST https://provider.com/oauth2/token
grant_type=authorization_code
&code=4/0Ab_5q...
&redirect_uri=http://127.0.0.1:52413/
&client_id=your-client-id
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Bir kullanıcıyı yaklaşık yirmi satırda oturum açtırın

Bileşeni oluşturun, auth2CodePKCE seçin, sağlayıcının iki uç noktasına yöneltin, OnAfterAccessToken olayını bağlayın, Start çağırın. Tarayıcı açılır, kullanıcı onay verir, olay token ile tetiklenir.

uses
  Classes, SysUtils,
  // sgc
  sgcHTTP, sgcHTTP_OAuth_Types;

// OAuth2 is a form field: OAuth2: TsgcHTTP_OAuth2_Client;
procedure TForm1.SignIn;
begin
  OAuth2 := TsgcHTTP_OAuth2_Client.Create(nil);
  OAuth2.OnAfterAccessToken := OnAfterAccessToken;
  OAuth2.OnErrorAccessToken := OnErrorAccessToken;

  // PKCE. The verifier and the S256 challenge are generated internally.
  OAuth2.OAuth2Options.GrantType := auth2CodePKCE;
  OAuth2.OAuth2Options.ClientId := 'your-client-id';

  // The two endpoints from the provider's documentation.
  OAuth2.AuthorizationServerOptions.AuthURL :=
    'https://provider.com/oauth2/authorize';
  OAuth2.AuthorizationServerOptions.TokenURL :=
    'https://provider.com/oauth2/token';
  OAuth2.AuthorizationServerOptions.Scope.Clear;
  OAuth2.AuthorizationServerOptions.Scope.Add('openid');
  OAuth2.AuthorizationServerOptions.Scope.Add('profile');

  // Loopback redirect. Port 0 asks the OS for a free port.
  OAuth2.LocalServerOptions.IP := '127.0.0.1';
  OAuth2.LocalServerOptions.Port := 0;

  OAuth2.Start; // opens the browser and returns immediately
end;

procedure TForm1.OnAfterAccessToken(Sender: TObject; const Access_Token,
  Token_Type, Expires_In, Refresh_Token, Scope, RawParams: String;
  var Handled: Boolean);
begin
  Memo1.Lines.Add('Signed in. Token expires in ' + Expires_In + ' s');
  SaveRefreshToken(Refresh_Token); // your own storage, see below
end;

procedure TForm1.OnErrorAccessToken(Sender: TObject; const Error,
  Error_Description, Error_URI, RawParams: String);
begin
  Memo1.Lines.Add('Sign-in failed: ' + Error + ' / ' + Error_Description);
end;
// include: sgcHTTP.hpp, sgcHTTP_OAuth_Types.hpp
TsgcHTTP_OAuth2_Client *OAuth2 = new TsgcHTTP_OAuth2_Client(this);
OAuth2->OnAfterAccessToken = OnAfterAccessToken;
OAuth2->OnErrorAccessToken = OnErrorAccessToken;

OAuth2->OAuth2Options->GrantType = auth2CodePKCE;
OAuth2->OAuth2Options->ClientId = "your-client-id";

OAuth2->AuthorizationServerOptions->AuthURL =
  "https://provider.com/oauth2/authorize";
OAuth2->AuthorizationServerOptions->TokenURL =
  "https://provider.com/oauth2/token";
OAuth2->AuthorizationServerOptions->Scope->Clear();
OAuth2->AuthorizationServerOptions->Scope->Add("openid");
OAuth2->AuthorizationServerOptions->Scope->Add("profile");

OAuth2->LocalServerOptions->IP = "127.0.0.1";
OAuth2->LocalServerOptions->Port = 0;

OAuth2->Start();

void __fastcall TForm1::OnAfterAccessToken(TObject *Sender,
  const UnicodeString Access_Token, const UnicodeString Token_Type,
  const UnicodeString Expires_In, const UnicodeString Refresh_Token,
  const UnicodeString Scope, const UnicodeString RawParams, bool &Handled)
{
  Memo1->Lines->Add("Signed in. Token expires in " + Expires_In + " s");
}

Bir masaüstü uygulamasında yönlendirme nereye gider

Web tarafındaki OAuth2'den geldiğinizde bariz bir yanıtı olmayan kısım budur ve ilk denemelerin çoğunun yanlış yaptığı yer de burasıdır.

Loopback, genel bir URL değil

Bir masaüstü uygulamasının yönlendirilecek bir alan adı yoktur. Kabul gören yanıt, ve bu bileşenin uyguladığı yanıt, bir loopback yönlendirmesidir: uygulama 127.0.0.1 üzerinde küçük bir HTTP dinleyicisi başlatır, o adresi yönlendirme URI'si olarak kaydeder ve kod gelir gelmez dinleyiciyi kapatır.

LocalServerOptions.IP varsayılan olarak 127.0.0.1, LocalServerOptions.Port ise varsayılan olarak 8080 değerindedir. Dağıtacağınız bir masaüstü uygulaması için bunun yerine Port := 0 yapın: işletim sistemi boş bir geçici port verir, bileşen gönderdiği yönlendirme URI'sine o portu koyar ve aynı makinedeki iki kopya asla çakışmaz.

Sağlayıcı çıplak bir ana bilgisayar ve port yerine tam olarak kayıtlı bir yol istiyorsa LocalServerOptions.RedirectURL değerini kaydettiğiniz değere ayarlayın. O dize hesaplanan değeri geçersiz kılar. Sabit bir yol sabit bir port anlamına gelir, dolayısıyla portu da kaydedin ve Port := 0 hilesini bırakın.

Dinleyici yalnızca akış beklerken ayaktadır. Hiç yönlendirme gerektirmeyen auth2ClientCredentials, auth2ResourceOwnerPassword veya auth2DeviceCode için hiç başlatılmaz.

redirect.pas
// Recommended for a shipped desktop app:
// random free port, no collisions, no registration of a port
OAuth2.LocalServerOptions.IP := '127.0.0.1';
OAuth2.LocalServerOptions.Port := 0;

// When the provider requires an exact registered redirect URI:
OAuth2.LocalServerOptions.IP := '127.0.0.1';
OAuth2.LocalServerOptions.Port := 8080;
OAuth2.LocalServerOptions.RedirectURL := 'http://localhost:8080/oauth/';

// Replace the browser page the user is left looking at
OAuth2.OnHTTPResponse := OnHTTPResponse;

procedure TForm1.OnHTTPResponse(Sender: TObject; var Code: Integer;
  var Text: String);
begin
  Code := 200;
  Text := '<html><body>You are signed in. ' +
          'Close this tab and return to the app.</body></html>';
end;

Token'ı okuyun, sonra işe koşun

OnAfterAccessToken tetiklendikten sonra aynı değerler salt okunur özellikler olarak erişilebilir kalır ve bileşen bunları, siz hiçbir başlığa dokunmadan HTTP ve WebSocket istemcilerinize verebilir.

Özellikler ve otomatik Bearer başlıkları

Olay parametreleri elverişlidir, ama tek kopya değildir. AccessToken, TokenType, CurrentExpiresIn ve CurrentRefreshToken aynı değerleri bileşen yaşadığı sürece tutar, dolayısıyla kodunuzun başka bir yerindeki bir işleyici onları elden ele taşımadan okuyabilir.

RawParams, token uç noktasından gelen dokunulmamış JSON gövdesidir. Bir sağlayıcı standart kümenin dışında bir şey döndürdüğünde, örneğin OpenID Connect için bir id_token, onu buradan ayrıştırın. Bileşen sizin için bir ID token'ı çözmez.

Her isteğin token'ı otomatik taşımasını istiyorsanız OAuth2 bileşenini TsgcHTTP1Client, TsgcHTTP2Client veya TsgcWebSocketClient üzerindeki Authentication.Token.OAuth alanına atayın. İstemci, sağlayıcının döndürdüğü token_type ne ise onu kullanarak sizin adınıza Authorization: Bearer <token> gönderir.

use-token.pas
var
  vHTTP: TsgcHTTP1Client;
begin
  // Read the tokens at any time after the flow completed
  Memo1.Lines.Add(OAuth2.AccessToken);
  Memo1.Lines.Add(OAuth2.TokenType);           // normally 'Bearer'
  Memo1.Lines.Add(IntToStr(OAuth2.CurrentExpiresIn));
  Memo1.Lines.Add(OAuth2.CurrentRefreshToken);

  // Let the HTTP client attach the Authorization header itself
  vHTTP := TsgcHTTP1Client.Create(nil);
  vHTTP.Authentication.Token.OAuth := OAuth2;
  Memo1.Lines.Add(vHTTP.Get('https://api.provider.com/v1/me'));
end;

Yenileme, böylece tarayıcı ikinci kez açılmaz

Bir erişim token'ı dakikalar yaşar. Bir yenileme token'ı haftalar veya aylar yaşar. İkincisini saklamak, bir oturum açmayı bir oturuma dönüştüren şeydir.

İki farklı sorun

Uygulamanın tek bir çalışması içinde yapılacak bir şey yok. Token uç noktası hem bir yenileme token'ı hem de bir expires_in döndürdüğünde bileşen, o ömrün kabaca yarısında bir iç zamanlayıcı kurar ve zamanlayıcı çaldığında, erişim token'ı ölmeden çok önce, grant_type=refresh_token gönderir. OnAfterRefreshToken yeni çiftle tetiklenir, sağlayıcı reddederse OnErrorRefreshToken tetiklenir. OnAfterAccessToken olayının Handled parametresine dokunmayın: onu True yapmak bileşene işi devraldığınızı söyler, o da ne yenileme token'ını saklar ne de o zamanlayıcıyı kurar.

Yeniden başlatmalar arasında iş size düşer, çünkü bir sırrın kullanıcılarınızın makinelerinde nereye yazılabileceğini yalnızca siz bilirsiniz. Yenileme token'ını kalıcı hale getirin, sonraki açılışta Start adımını tamamen atlayın ve saklanan değerle Refresh çağırın. Hiçbir tarayıcı açılmaz ve kullanıcı, ana formunuz çizilmeden önce oturum açmış olur.

Yenileme token'larını döndüren sağlayıcılar her yenilemede size yenisini verir, dolayısıyla her OnAfterRefreshToken olayında sakladığınızın üzerine yazın. Saklanan token sonunda reddedildiğinde Start adımına dönün ve kullanıcının yeniden oturum açmasına izin verin.

Kullanıcıyı düzgün biçimde çıkarmak için Revoke, bir token'ın henüz geçerli olup olmadığını sağlayıcıya sormak için Introspect kullanın. İkisi de AuthorizationServerOptions içinde eşleşen uç noktanın ayarlanmasını ister.

refresh.pas
procedure TForm1.FormCreate(Sender: TObject);
var
  vStored: string;
begin
  ConfigureOAuth2; // same settings as the QuickStart
  OAuth2.OnAfterRefreshToken := OnAfterRefreshToken;
  OAuth2.OnErrorRefreshToken := OnErrorRefreshToken;

  vStored := LoadRefreshToken;
  if vStored <> '' then
    OAuth2.Refresh(vStored)  // silent, no browser
  else
    OAuth2.Start;            // first run, ask the user
end;

procedure TForm1.OnAfterRefreshToken(Sender: TObject; const Access_Token,
  Token_Type, Expires_In, Refresh_Token, Scope, RawParams: String;
  var Handled: Boolean);
begin
  // providers that rotate hand back a new refresh token
  if Refresh_Token <> '' then
    SaveRefreshToken(Refresh_Token);
end;

procedure TForm1.OnErrorRefreshToken(Sender: TObject; const Error,
  Error_Description, Error_URI, RawParams: String);
begin
  ClearStoredRefreshToken;
  OAuth2.Start; // the stored token is dead, prompt again
end;

// Signing out
OAuth2.AuthorizationServerOptions.RevocationURL :=
  'https://provider.com/oauth2/revoke';
OAuth2.Revoke(OAuth2.CurrentRefreshToken, 'refresh_token');

Token'ları ortalıkta bırakmadan saklamak

sgcWebSockets bilerek hiçbir token kasası ile gelmez. Bir kimlik bilgisinin nereye yazılabileceği, kullanıcılarınıza ve dağıtımınıza dair bir karardır, bu yüzden kütüphane size token'ı verir ve durur.

Erişim token'ını yalnızca bellekte tutun

Dakikalar içinde süresi dolar ve yenileme token'ı her zaman bir yenisini basabilir. Onu diske yazmak için hiçbir neden yok, yazmamak için ise her neden var.

Yenileme token'ını kullanıcı başına şifreleyin

Windows'ta DPAPI (CryptProtectData) şifreli metni Windows hesabına bağlar, dolayısıyla kopyalanan bir dosya başka bir makinede işe yaramaz. macOS'ta Keychain, güncel Linux masaüstlerinde ise Secret Service vardır.

Güvendiğiniz bir client secret'ı asla dağıtmayın

Dağıtılan bir çalıştırılabilir dosyanın içindeki her şey herkese açıktır. PKCE'nin tüm dayanağı budur. Sağlayıcınız bir masaüstü istemcisi için sır veriyorsa onu koruma değil, bir tanımlayıcı olarak görün.

Dosyayı bir kimlik bilgisi gibi sınırlayın

Kullanıcı başına uygulama verisi; Program Files değil, çalıştırılabilir dosyanın yanı değil, paylaşılan bir ağ yolu değil ve kaynak kontrolüne işlenmiş düz bir INI hiç değil.

Oturum kapatınca silin

Sağlayıcının token'ı geçersiz kılması için Revoke çağırın, sonra saklanan kopyayı kaldırın. Diskte kalan iptal edilmiş bir token yine de bir denetim bulgusudur.

Sırları günlüğün dışında tutun

HTTPClientOptions.LogOptions yetkilendirme sunucusuna giden trafiği yazar. Akışı çalışır hale getirirken paha biçilmezdir ve token dolu bir dosyadır. Dağıtmadan önce kapatın.

Hangi sağlayıcı neyi ister

Her OAuth 2.0 sağlayıcısı aynı bir avuç ayarı ister: iki uç nokta, bir client id, kapsamlar ve kayıtlı bir yönlendirme. Google ve Microsoft'un ayrıca uç noktaları dolduran ve size bir kullanıcı profili döndüren hazır bileşenleri vardır.

Google ve Microsoft, tek çağrıda

TsgcHTTP_OAuth2_Client_Google ve TsgcHTTP_OAuth2_Client_Microsoft aynı temel bileşenden türer ve uç noktaları önceden doldurur. Authenticate metotları bloke edicidir: tüm akışı yürütür, tarayıcı gidiş dönüşünü bekler ve Authenticated ile doldurulmuş bir UserProfile içeren bir veri nesnesi döndürür.

"Bu kullanıcı kim" sorusuna giden mümkün olan en kısa yol budur. TsgcOAuth2_Google_Data.UserProfile şunları taşır: _Name, Given_Name, Family_Name, Id, Locale ve Picture. TsgcOAuth2_Microsoft_Data.UserProfile ise DisplayName, GivenName, Surname, Mail, JobTitle, OfficeLocation ve fazlasını taşır. Microsoft'un Authenticate metodu önce tenant id alır.

Diğer her sağlayıcı için temel TsgcHTTP_OAuth2_Client bileşenini kullanın ve iki URL'yi belgelerinden kopyalayın. Bundan sonra sağlayıcıya özgü hiçbir şey kalmaz.

social-signin.pas
uses
  sgcHTTP, sgcHTTP_OAuth2_Client_Google;

var
  vClient: TsgcHTTP_OAuth2_Client_Google;
  vData: TsgcOAuth2_Google_Data;
begin
  vClient := TsgcHTTP_OAuth2_Client_Google.Create(nil);
  try
    vData := vClient.Authenticate('client-id', 'client-secret');
    if vData.Authenticated then
    begin
      ShowMessage(vData.UserProfile._Name);
      ShowMessage(vData.AccessToken);
    end;
  finally
    vClient.Free;
  end;
end;
Sağlayıcı Bileşen İzin türü Yönlendirme Client secret
Google TsgcHTTP_OAuth2_Client_Google veya temel istemci auth2CodePKCE Loopback, Port := 0 Masaüstü istemcileri için verilir, varsa ayarlayın
Microsoft Entra ID TsgcHTTP_OAuth2_Client_Microsoft veya temel istemci auth2CodePKCE Loopback, mobil / masaüstü platformu olarak kayıtlı Açık bir istemci tarafından kullanılmaz, boş bırakın
Auth0, Okta, Keycloak, AWS Cognito TsgcHTTP_OAuth2_Client auth2CodePKCE Loopback, uygulama üzerinde kayıtlı Uygulamanın açık mı yoksa gizli mi olduğuna bağlı
Arka plan işleri ve servisler TsgcHTTP_OAuth2_Client auth2ClientCredentials Yok, hiçbir tarayıcı devreye girmez Gerekli ve güvenli, çünkü hiçbir şey dağıtılmıyor
Kiosklar, TV'ler, ekransız kutular TsgcHTTP_OAuth2_Client auth2DeviceCode (RFC 8628) Yok, kullanıcı işi telefonda bitirir Genellikle gerekmez

E-posta göndermek için oturum açma: OAuth 2.0 ve XOAUTH2

Gmail ve Microsoft 365, SMTP, IMAP ve POP üzerinden parola kabul etmeyi bıraktı. Yerine geçen şey, SASL XOAUTH2 mekanizmasıyla sunulan, az önce edindiğiniz aynı erişim token'ıdır.

Token buradan gelir, SASL adımı sgcIndy'den gelir

Token'ı almak tam olarak yukarıdaki akıştır: auth2CodePKCE, bir loopback yönlendirmesi ve AuthorizationServerOptions.Scope içinde https://mail.google.com/ gibi bir posta kapsamı. Posta senaryosunun hiçbir yanı OAuth2 tarafını değiştirmez.

Onu sunmak işin diğer yarısıdır. sgcIndy, IdSASLXOAUTH2 biriminde TIdSASLXOAUTH2 ile gelir. Bunu TIdSMTP.SASLMechanisms listesine ekleyin, AuthType := satSASL yapın ve kullanıcı adını ve erişim token'ını bileşenin OnAuthenticate olayından verin. Aynı mekanizma TIdIMAP4 ve TIdPOP3 için de çalışır.

İki bileşeni zihninizde ayrı tutun: OAuth2 istemcisi bir token'ı nasıl edineceğini ve yenileyeceğini bilir, SASL mekanizması ise bir token'ı nasıl sunacağını bilir. Hiçbirinin diğerini bilmesine gerek yoktur.

smtp-xoauth2.pas
uses
  IdSMTP, IdSASLXOAUTH2;

var
  vSASL: TIdSASLXOAUTH2;
  vSMTP: TIdSMTP;
begin
  vSASL := TIdSASLXOAUTH2.Create(nil);
  vSASL.OnAuthenticate := OnXOAuth2Authenticate;

  vSMTP := TIdSMTP.Create(nil);
  vSMTP.AuthType := satSASL;
  vSMTP.SASLMechanisms.Clear;
  vSMTP.SASLMechanisms.Add.SASL := vSASL;
end;

procedure TForm1.OnXOAuth2Authenticate(Sender: TObject;
  var Username: string; var Token: string);
begin
  Username := 'user@example.com';
  Token := OAuth2.AccessToken; // from the PKCE flow above
end;

Bir istemci mi gerekiyor, yoksa bir sunucu da mı?

Yukarıdaki her şey istemci tarafıdır. İkinci yarıya yalnızca token'ları veren taraf sizseniz ihtiyacınız olur.

Yalnızca istemci

Kullanıcıları başkasının kimlik sağlayıcısında oturum açtırıyorsanız, Google, Microsoft, Auth0, Okta, Keycloak, AWS Cognito, kendi kurumsal SSO'nuz, TsgcHTTP_OAuth2_Client dışında hiçbir şeye ihtiyacınız yoktur. O bileşen Standard sürümüne ve üstündeki her sürüme derlenir. Ayrıca ihtiyaç duyduğu çalışma zamanıyla paketlenmiş olarak bağımsız sgcAuth paketinde de tek başına satılır.

Yaygın olan durum budur ve bu sayfanın buraya kadarki tamamı da odur.

Bir sunucuya da ihtiyaç duyduğunuzda

Sunucu yarısına yalnızca kendi uygulamanız yetkilendirme sunucusu olduğunda ihtiyaç duyarsınız: client id'leri siz verirsiniz, oturum açma sayfasını siz barındırırsınız, API'nizin sonra güvendiği erişim token'larını siz basar ve iptal edersiniz. Bu, bir TsgcWebSocketHTTPServer bileşenine bağlanan TsgcHTTP_OAuth2_Server bileşenidir ve bir Enterprise bileşenidir.

PKCE'yi varsayılan olarak doğrular. OAuth2Options.PKCE kutudan True çıkar, dolayısıyla challenge gönderen bir istemci eşleşen bir verifier üretmek zorundadır, üretemeyen reddedilir. İstemci uygulamalarını Apps.AddApp ile kaydedin, kullanıcıları OnOAuth2Authentication içinde doğrulayın ve yeniden başlatmalar arasında token'ları AddToken ile geri yükleyin.

Aynı Enterprise katmanı, uç noktalarınızda JWT hamiline yazılı token'ları doğrulamak için TsgcHTTP_JWT_Server ve passkey'ler için TsgcWSAPIServer_WebAuthn bileşenlerini taşır. Eşleşen istemciler, TsgcHTTP_OAuth2_Client ve TsgcHTTP_JWT_Client, Standard ve üstündedir. İstemci ve sunucu farklı sürüm katmanlarında yer alır; ikisinden biri üzerine plan yapmadan önce bunu kontrol etmekte fayda var.

own-server.pas
uses
  sgcWebSocket, sgcWebSocket_Classes, sgcHTTP,
  sgcHTTP_OAuth_Types, sgcHTTP_OAuth2_Server;

var
  vOAuth2: TsgcHTTP_OAuth2_Server;
  vServer: TsgcWebSocketHTTPServer;
begin
  vOAuth2 := TsgcHTTP_OAuth2_Server.Create(nil);
  vOAuth2.OAuth2Options.PKCE := True; // default
  vOAuth2.OnOAuth2Authentication := OnOAuth2Authentication;
  vOAuth2.Apps.AddApp('MyDesktopApp', 'http://127.0.0.1:8080',
    'my-client-id', 'my-client-secret', 3600, True,
    [auth2Code, auth2CodePKCE]);

  vServer := TsgcWebSocketHTTPServer.Create(nil);
  vServer.Authentication.Enabled := True;
  vServer.Authentication.OAuth.OAuth2 := vOAuth2;
  vServer.Port := 8080;
  vServer.Active := True;
end;

procedure TForm1.OnOAuth2Authentication(Connection: TsgcWSConnection;
  OAuth2: TsgcHTTPOAuth2Request; aUser, aPassword: String;
  var Authenticated: Boolean);
begin
  Authenticated := CheckUserInYourDatabase(aUser, aPassword);
end;

İlk seferde genellikle ne ters gider

Masaüstünde OAuth2'nin başarısız ilk denemelerinin neredeyse tamamı şu altısından biridir.

redirect_uri_mismatch

Bileşenin gönderdiği URI, kaydettiğinizle karakter karakter eşleşmelidir; sondaki eğik çizgi ve port dahil. Sabit bir URI kaydettiyseniz hesaplanan değere güvenmek yerine LocalServerOptions.RedirectURL değerini tam olarak o dizeye ayarlayın. Sağlayıcı herhangi bir loopback portuna izin veriyorsa Port := 0 kullanın ve yalnızca ana bilgisayarı kaydedin.

Tarayıcı açılıyor ama hiçbir şey dönmüyor

Ya bir şey portu tutuyordur ya da bir güvenlik duvarı kuralı loopback dinleyicisini engelliyordur. Port := 0 yapın ve akışın önceki bir çalışmasının dinlemede bırakılmak yerine Stop ile sonlandırıldığını doğrulayın.

Token takasında invalid_grant

Yetkilendirme kodları tek kullanımlıktır ve kısa ömürlüdür. Yönlendirme ile takas arasına kesme noktası koyarak hata ayıklamak kodun süresini doldurur. Tahmin yürütmek yerine hatayı, sağlayıcının kendi error ve error_description değerlerini veren OnErrorAccessToken olayından okuyun.

Hiç yenileme token'ı dönmedi

Sağlayıcılar yalnızca istediğinizde verir. Google access_type=offline ister, Microsoft offline_access kapsamını ister. Kapsamı AuthorizationServerOptions.Scope listesine ekleyin ya da OnBeforeAuthorizeCode içinde URL parametresini düzenleyerek sorgu parametresini ekleyin.

Linux'ta veya mobilde TLS başarısız oluyor

Token takası bir HTTPS POST'udur, dolayısıyla çalışan bir TLS arka ucuna ihtiyaç duyar. Bunu HTTPClientOptions.TLSOptions.IOHandler seçer: iohOpenSSL, Windows'ta dağıtılacak DLL istemeyen iohSChannel ya da Enterprise sürümündeki yerel iohAndroidTLS ve iohAppleTLS işleyicileri.

Oturum açma sayfasını uygulamanın içinde istiyordunuz

OnBeforeAuthorizeCode olayını ele alın, Handled := True yapın ve kendi TsgcWebView2 veya TWebBrowser bileşeninizi size verilen URL adresine götürün. Loopback dinleyicisi yönlendirmeyi yine yakalar. Birkaç sağlayıcının artık onay ekranını gömülü bir tarayıcıda göstermeyi reddettiğini unutmayın.

Delphi OAuth2 ve PKCE soruları

Geliştiricilerin başlamadan önce gerçekten arattığı sorular.

Bir TsgcHTTP_OAuth2_Client bırakın, OAuth2Options.GrantType := auth2CodePKCE yapın, OAuth2Options.ClientId, AuthorizationServerOptions.AuthURL, AuthorizationServerOptions.TokenURL ve AuthorizationServerOptions.Scope alanlarını doldurun, LocalServerOptions.IP değerini 127.0.0.1 ve LocalServerOptions.Port değerini 0 yapın, sonra Start çağırın. Bileşen PKCE değerlerini üretir, tarayıcıyı açar, yönlendirmeyi bir loopback dinleyicisinde yakalar, kodu takas eder ve token ile OnAfterAccessToken olayını tetikler.
Üretmeniz gerekmiyor. GrantType değeri auth2CodePKCE olduğunda TsgcHTTP_OAuth2_Client, platformun kriptografik rastgelelik kaynağından 32 bayt çeker, bunları Base64URL ile 43 karakterlik code verifier'a kodlar, code challenge değerini o verifier'ın SHA-256 özetinin Base64URL kodlamasına ayarlar ve code_challenge_method değerini S256 olarak sabitler. Verifier bileşenin içinde gizli tutulur ve token takasında yeniden oynatılır, dolayısıyla yönlendirmede hiç görünmez. Çifti başka bir amaç için elle kurmak isterseniz aynı yapı taşları açıktır: sgcCrypto_Random birimindeki sgcRandomBytes ile sgcBase_Helpers birimindeki GetHashSHA256 ve EncodeBase64URL.
Bir loopback adresi. TsgcHTTP_OAuth2_Client, yalnızca akış çalışırken LocalServerOptions.IP ve LocalServerOptions.Port üzerinde küçük bir HTTP dinleyicisi başlatır ve gönderdiği yönlendirme URI'si bu değerlerden kurulur. Varsayılanlar 127.0.0.1 ve 8080 portudur. Dağıtacağınız bir uygulamada Port değerini 0 yapın; böylece işletim sistemi boş bir geçici port seçer ve iki örnek asla tek bir port için çekişmez. Sağlayıcı tam olarak kayıtlı bir URI istiyorsa o dizeyi LocalServerOptions.RedirectURL içine koyun, hesaplanan değeri geçersiz kılar.
Sağlayıcıya bağlı. PKCE tam da dağıtılan bir masaüstü uygulaması sır saklayamadığı için vardır, dolayısıyla açık bir istemci normalde hiç sır göndermez ve OAuth2Options.ClientSecret alanını boş bırakır. Bazı sağlayıcılar masaüstü istemcileri için yine de bir sır verir ve token isteğinde bunu bekler. Verdiklerinde ayarlayın, ama onu bir koruma değil bir tanımlayıcı olarak görün, çünkü dağıtılan bir çalıştırılabilir dosyanın içindeki her şey çıkarılabilir.
Yenileme token'ını kalıcı hale getirin, sonraki açılışta Start yerine onunla Refresh çağırın. Onu OnAfterAccessToken olayının Refresh_Token parametresinden ya da daha sonra CurrentRefreshToken özelliğinden okuyun. Her OnAfterRefreshToken olayında saklanan kopyanın üzerine yazın, çünkü yenileme token'larını döndüren sağlayıcılar eskisini geçersiz kılar. Tek bir çalışma içinde hiçbir iş gerekmez: bileşen expires_in değerinden bir zamanlayıcı kurar ve erişim token'ını kendi kendine yeniler.
Erişim token'ını yalnızca bellekte tutun, dakikalar içinde süresi dolar ve her zaman yeniden basılabilir. Yenileme token'ını şifreli ve geçerli kullanıcıya sınırlanmış olarak saklayın, örneğin Windows'ta DPAPI, macOS'ta Keychain veya Linux'ta Secret Service ile, çalıştırılabilir dosyanın yanında değil kullanıcı başına uygulama verisinde. sgcWebSockets bilerek kendi token kasasıyla gelmez: size token'ı verir ve saklama kararını size bırakır. Dağıtmadan önce HTTPClientOptions.LogOptions ayarını kapatmayı unutmayın, çünkü o günlük token'ları içerir.
Bu sayfadaki akışla, sağlayıcının https://mail.google.com/ gibi posta kapsamını isteyerek bir erişim token'ı edinin, sonra onu SASL XOAUTH2 ile sunun. sgcIndy, IdSASLXOAUTH2 biriminde TIdSASLXOAUTH2 ile gelir. Bunu TIdSMTP.SASLMechanisms listesine ekleyin, AuthType := satSASL yapın ve kullanıcı adını ve erişim token'ını bileşenin OnAuthenticate olayından döndürün. Aynı mekanizma TIdIMAP4 ve TIdPOP3 bileşenlerinin kimliğini de doğrular.
OAuth2 istemcisi ve JWT istemcisi Standard sürümüne ve üstündeki her sürüme derlenir, yani Standard, Professional, Enterprise ve All-Access hepsi bunları içerir. OAuth2 sunucusu, JWT sunucusu ve WebAuthn sunucusu Enterprise bileşenleridir ve Standard ya da Professional derlemelerinde bulunmaz. İki istemci bileşeni ayrıca, ihtiyaç duydukları çalışma zamanıyla paketlenmiş olarak bağımsız sgcAuth paketi biçiminde de satılır.
Yalnızca token'ları veren taraf sizseniz. Kullanıcıları Google, Microsoft, Auth0, Okta, Keycloak, AWS Cognito veya kurumsal bir kimlik sağlayıcısında oturum açtırmak için istemci bileşeninden başka bir şey gerekmez. TsgcHTTP_OAuth2_Server bileşenine, kendi uygulamanız client id'leri kaydettiğinde, oturum açma sayfasını barındırdığında ve API'nizin güvendiği token'ları bastığında ihtiyaç duyarsınız. PKCE'yi varsayılan olarak OAuth2Options.PKCE üzerinden doğrular, uygulamaları Apps.AddApp ile kaydeder ve Authentication.OAuth.OAuth2 üzerinden bir TsgcWebSocketHTTPServer bileşenine bağlanır.
Evet. Tam olarak kurulmuş yetkilendirme URL'sini bir var parametresi olarak alan OnBeforeAuthorizeCode olayını ele alın, bileşenin sistem tarayıcısını başlatmaması için Handled := True yapın ve TsgcWebView2 gibi gömülü bir kontrolü o URL'ye götürün. Loopback dinleyicisi yönlendirmeyi yine alır ve akış normal biçimde tamamlanır. Birkaç sağlayıcının artık onay ekranını gömülü tarayıcılarda engellediğini bilin; sistem tarayıcısının varsayılan olmasının nedeni budur.
Evet. TsgcHTTP_OAuth2_Client, Delphi 7'den Delphi 13'e ve eşleşen C++ Builder sürümlerine kadar, VCL, FireMonkey ve Lazarus / FPC ortamlarında Windows, macOS, Linux, iOS ve Android için derlenir. Tarayıcıyı açmak, platformun sunduğu neyse onu kullanır. Platforma özgü tek seçim, token takası için HTTPClientOptions.TLSOptions.IOHandler üzerinden seçilen TLS arka ucudur.

Referans, demo ve belgeler

Bileşen referansı, çalıştırmaya hazır demo projesi ve bu sayfadan daha derine inen teknik belgeler.

Çevrimiçi Yardım, TsgcHTTP_OAuth2_Client İstemci bileşeninin her özelliği, metodu ve olayı, Authorization Code + PKCE konusuyla birlikte.
Çevrimiçi Yardım, PKCE ile Authorization Code İzin türü konusu: PKCE ne yapar, yapılandırma tablosu ve rastgele port önerisi.
Demo Projesi, Demos\20.HTTP_Protocol\02.OAuth2_Authentication Gmail, Google Pub/Sub, Azure AD, AWS Cognito, Dropbox ve Auth0 için çalışan hazır ayarlar içeren istemci ve sunucu projeleri, ayrıca gömülü tarayıcılı bir varyant.
Teknik Belge, OAuth2 İstemcisi (PDF) Özellikler, hızlı başlangıç, her izin türü ve Delphi, C++ Builder ile .NET için kod örnekleri.
Teknik Belge, OAuth2 Sunucusu (PDF) Enterprise yetkilendirme sunucusu bileşeni: uç noktalar, uygulama kaydı, PKCE doğrulaması ve token yaşam döngüsü.
Kullanıcı Kılavuzu (PDF) Kütüphanedeki her bileşeni kapsayan kapsamlı kılavuz.

Bu akışın uyguladığı belirtimler

Bir sağlayıcının destek masasıyla tartışmayı bitirmeniz gerektiğinde birincil kaynaklar.

Bu sayfanın arkasındaki bileşenler ve yazılar

Bileşen sayfaları tam özellik listesini taşır, yazılar ise bu sayfanın yalnızca değindiği durumları ele alır.

OAuth2 İstemcisi bileşeni

Device Code ve DPoP dahil, TsgcHTTP_OAuth2_Client bileşeninin tüm özellik, metot ve olay yüzeyi.

Daha fazla bilgi →

OAuth2 Sunucusu bileşeni

Enterprise yetkilendirme sunucusu: kendi authorize, token, revoke ve introspect uç noktalarınız.

Daha fazla bilgi →

sgcAuth

OAuth2 ve JWT istemci bileşenleri, ihtiyaç duydukları çalışma zamanı paketin içinde olacak şekilde bağımsız bir paket olarak.

Daha fazla bilgi →

JWT İstemcisi bileşeni

JSON Web Token'ları imzalayın ve iliştirin, tek başına ya da HTTP ve WebSocket istemcileriniz için Bearer kaynağı olarak.

Daha fazla bilgi →

Delphi'de PKCE OAuth2

Hem istemci hem de sunucu bileşenlerinde PKCE desteğini tanıtan özgün sürüm yazısı.

Yazıyı okuyun →

sgcIndy XOAuth2

SASL XOAUTH2 mekanizmasıyla SMTP, IMAP ve POP üzerinden OAuth 2.0 erişim token'ı kullanarak posta göndermek.

Yazıyı okuyun →

OAuth2 Client Credentials

Arka plan servisleri ve makineden makineye API erişimi için kullanıcısız varyant.

Yazıyı okuyun →

Delphi'de OAuth2 DPoP

RFC 9449 uyarınca sahiplik kanıtı isteyen sağlayıcılar için bir erişim token'ını bir anahtar çiftine bağlamak.

Yazıyı okuyun →

AWS Cognito ve OAuth2

Gerçek bir kimlik sağlayıcısına karşı, uç nokta uç nokta işlenmiş bir yapılandırma.

Yazıyı okuyun →

OAuth2 Sunucusu: uygulama kaydı

Kendi yetkilendirme sunucunuzda istemci uygulamalarını, yönlendirme URI'lerini ve izin verilen izin türlerini kaydetmek.

Yazıyı okuyun →

Dış sağlayıcılarla yetkilendirme

Kendi sunucunuzun oturum açmayı Google'a, Microsoft'a veya başka bir dış kimlik sağlayıcısına devretmesi.

Yazıyı okuyun →

WebAuthn ve passkey'ler

Hiç token devri istemediğinizde parolasız alternatif.

Daha fazla bilgi →

Bu sayfa, her biri tek bir işi baştan sona ele alan Delphi kullanım senaryoları arasındadır. Şimdiye kadarki diğerleri Delphi'den bir LLM çağırma ve iki uygulamayı WebRTC ile eşler arası bağlama.

İlk kullanıcınızı bugün oturum açtırın

Ücretsiz denemeyi indirin, OAuth2 demosunu açın, kendi sağlayıcınıza yöneltin ve tarayıcı gidiş dönüşünün tamamlanmasını izleyin.