Laravel AT Protocol Client (alpha & unstable)
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update documentation for DID-based credential storage

+28 -30
+28 -30
README.md
··· 166 166 use SocialDept\AtpClient\Events\TokenRefreshed; 167 167 168 168 Event::listen(TokenRefreshed::class, function ($event) { 169 - // $event->identifier - the user identifier 169 + // $event->did - the user's DID (e.g., did:plc:abc123...) 170 170 // $event->token - the new AccessToken 171 171 // Update your credential storage here 172 172 }); ··· 489 489 490 490 If you lose the refresh token, the user must re-authenticate. The `CredentialProvider` ensures tokens are safely persisted. 491 491 492 + ### How Handle Resolution Works 493 + 494 + When you call `Atp::as('user.bsky.social')` or `Atp::login('user.bsky.social', $password)`, the package automatically resolves the handle to a DID (Decentralized Identifier). The DID is then used as the storage key for credentials. This ensures consistency even if a user changes their handle. 495 + 496 + If resolution fails (invalid handle, network error, etc.), a `HandleResolutionException` is thrown. 497 + 492 498 ### The CredentialProvider Interface 493 499 494 500 ```php 495 501 interface CredentialProvider 496 502 { 497 - // Get stored credentials for a user 498 - public function getCredentials(string $identifier): ?Credentials; 503 + // Get stored credentials by DID 504 + public function getCredentials(string $did): ?Credentials; 499 505 500 506 // Store credentials after initial OAuth or app password login 501 - public function storeCredentials(string $identifier, AccessToken $token): void; 507 + public function storeCredentials(string $did, AccessToken $token): void; 502 508 503 509 // Update credentials after token refresh (CRITICAL: refresh tokens are single-use!) 504 - public function updateCredentials(string $identifier, AccessToken $token): void; 510 + public function updateCredentials(string $did, AccessToken $token): void; 505 511 506 512 // Remove credentials (logout) 507 - public function removeCredentials(string $identifier): void; 513 + public function removeCredentials(string $did): void; 508 514 } 509 515 ``` 510 516 ··· 519 525 ```php 520 526 Schema::create('atp_credentials', function (Blueprint $table) { 521 527 $table->id(); 522 - $table->string('identifier')->unique(); // User handle or DID 523 - $table->string('did'); // Decentralized identifier 528 + $table->string('did')->unique(); // Decentralized identifier (primary key) 524 529 $table->string('handle')->nullable(); // User's handle (e.g., user.bsky.social) 525 530 $table->string('issuer')->nullable(); // PDS endpoint URL 526 531 $table->text('access_token'); // JWT access token 527 532 $table->text('refresh_token'); // Single-use refresh token 528 533 $table->timestamp('expires_at'); // Token expiration time 529 534 $table->timestamps(); 530 - 531 - $table->index('did'); 532 535 }); 533 536 ``` 534 537 ··· 546 549 547 550 class DatabaseCredentialProvider implements CredentialProvider 548 551 { 549 - public function getCredentials(string $identifier): ?Credentials 552 + public function getCredentials(string $did): ?Credentials 550 553 { 551 - $record = AtpCredential::where('identifier', $identifier)->first(); 554 + $record = AtpCredential::where('did', $did)->first(); 552 555 553 556 if (! $record) { 554 557 return null; 555 558 } 556 559 557 560 return new Credentials( 558 - identifier: $record->identifier, 559 561 did: $record->did, 560 562 accessToken: $record->access_token, 561 563 refreshToken: $record->refresh_token, ··· 565 567 ); 566 568 } 567 569 568 - public function storeCredentials(string $identifier, AccessToken $token): void 570 + public function storeCredentials(string $did, AccessToken $token): void 569 571 { 570 572 AtpCredential::updateOrCreate( 571 - ['identifier' => $identifier], 573 + ['did' => $did], 572 574 [ 573 - 'did' => $token->did, 574 575 'handle' => $token->handle, 575 576 'issuer' => $token->issuer, 576 577 'access_token' => $token->accessJwt, ··· 580 581 ); 581 582 } 582 583 583 - public function updateCredentials(string $identifier, AccessToken $token): void 584 + public function updateCredentials(string $did, AccessToken $token): void 584 585 { 585 - AtpCredential::where('identifier', $identifier)->update([ 586 + AtpCredential::where('did', $did)->update([ 586 587 'access_token' => $token->accessJwt, 587 588 'refresh_token' => $token->refreshJwt, 588 589 'expires_at' => $token->expiresAt, ··· 592 593 ]); 593 594 } 594 595 595 - public function removeCredentials(string $identifier): void 596 + public function removeCredentials(string $did): void 596 597 { 597 - AtpCredential::where('identifier', $identifier)->delete(); 598 + AtpCredential::where('did', $did)->delete(); 598 599 } 599 600 } 600 601 ``` ··· 611 612 class AtpCredential extends Model 612 613 { 613 614 protected $fillable = [ 614 - 'identifier', 615 615 'did', 616 616 'handle', 617 617 'issuer', ··· 681 681 Then update your provider to work with the authenticated user: 682 682 683 683 ```php 684 - public function storeCredentials(string $identifier, AccessToken $token): void 684 + public function storeCredentials(string $did, AccessToken $token): void 685 685 { 686 686 AtpCredential::updateOrCreate( 687 - ['identifier' => $identifier], 687 + ['did' => $did], 688 688 [ 689 689 'user_id' => auth()->id(), // Link to current user 690 - 'did' => $token->did, 691 690 'handle' => $token->handle, 692 691 'issuer' => $token->issuer, 693 692 'access_token' => $token->accessJwt, ··· 702 701 703 702 | Field | Description | 704 703 |-------|-------------| 705 - | `identifier` | The key used to look up credentials (usually the handle) | 706 - | `did` | Decentralized Identifier (e.g., `did:plc:abc123...`) | 707 - | `handle` | User's handle (e.g., `user.bsky.social`) | 704 + | `did` | Decentralized Identifier - the stable, permanent user ID (e.g., `did:plc:abc123...`) | 705 + | `handle` | User's handle (e.g., `user.bsky.social`) - can change | 708 706 | `issuer` | The user's PDS endpoint URL (avoids repeated lookups) | 709 707 | `accessToken` | JWT for API authentication (short-lived) | 710 708 | `refreshToken` | Token to get new access tokens (single-use!) | ··· 721 719 Event::listen(TokenRefreshed::class, function (TokenRefreshed $event) { 722 720 // The CredentialProvider.updateCredentials() is already called, 723 721 // but you can do additional logging or notifications here 724 - Log::info("Token refreshed for: {$event->identifier}"); 722 + Log::info("Token refreshed for: {$event->did}"); 725 723 }); 726 724 ``` 727 725 ··· 735 733 736 734 // Before token refresh 737 735 Event::listen(TokenRefreshing::class, function ($event) { 738 - Log::info('Refreshing token for: ' . $event->identifier); 736 + Log::info('Refreshing token for: ' . $event->did); 739 737 }); 740 738 741 739 // After token refresh 742 740 Event::listen(TokenRefreshed::class, function ($event) { 743 741 // Update your stored credentials 744 742 $this->credentialProvider->updateCredentials( 745 - $event->identifier, 743 + $event->did, 746 744 $event->token 747 745 ); 748 746 });