this repo has no description
0
fork

Configure Feed

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

more fixes, and indendation

+64 -75
+64 -75
atproto/auth/oauth/doc.go
··· 17 17 18 18 Create a single [ClientApp] instance during service setup that will be used (concurrently) across all users and sessions: 19 19 20 - ``` 21 - oauthScope := "atproto transition:generic" 22 - config := oauth.NewPublicConfig( 23 - "https://app.example.com/client-metadata.json", 24 - "https://app.example.com/oauth/callback", 25 - ) 20 + config := oauth.NewPublicConfig( 21 + "https://app.example.com/client-metadata.json", 22 + "https://app.example.com/oauth/callback", 23 + []string{"transition:generic"}, 24 + ) 26 25 27 - // clients are "public" by default, but if they have secure access to a secret attestation key can be "confidential" 28 - 29 - if CLIENT_SECRET_KEY != "" { 30 - priv, err := crypto.ParsePrivateMultibase(CLIENT_SECRET_KEY) 31 - if err != nil { 32 - return err 26 + // clients are "public" by default, but if they have secure access to a secret attestation key can be "confidential" 27 + if CLIENT_SECRET_KEY != "" { 28 + priv, err := crypto.ParsePrivateMultibase(CLIENT_SECRET_KEY) 29 + if err != nil { 30 + return err 31 + } 32 + if err := config.AddClientSecret(priv, "example1"); err != nil { 33 + return err 34 + } 33 35 } 34 - if err := config.AddClientSecret(priv, "example1"); err != nil { 35 - return err 36 - } 37 - } 38 36 39 - oauthApp := oauth.NewClientApp(&config, oauth.NewMemStore()) 40 - ``` 37 + oauthApp := oauth.NewClientApp(&config, oauth.NewMemStore()) 41 38 42 39 For a real service, you would want to use a database or other peristant storage instead of [MemStore]. Otherwise all user sessions are dropped every time the process restarts. 43 40 44 41 The client metadata document needs to be served at the URL indicated by the `client_id`. This can be done statically, or dynamically generated and served from the configuation: 45 42 46 - ``` 47 - http.HandleFunc("GET /client-metadata.json", HandleClientMetadata) 43 + http.HandleFunc("GET /client-metadata.json", HandleClientMetadata) 48 44 49 - func HandleClientMetadata(w http.ResponseWriter, r *http.Request) { 50 - doc := oauthApp.Config.ClientMetadata(oauthScope) 51 - w.Header().Set("Content-Type", "application/json") 52 - if err := json.NewEncoder(w).Encode(doc); err != nil { 53 - http.Error(w, err.Error(), http.StatusInternalServerError) 54 - return 45 + func HandleClientMetadata(w http.ResponseWriter, r *http.Request) { 46 + doc := oauthApp.Config.ClientMetadata() 47 + w.Header().Set("Content-Type", "application/json") 48 + if err := json.NewEncoder(w).Encode(doc); err != nil { 49 + http.Error(w, err.Error(), http.StatusInternalServerError) 50 + return 51 + } 55 52 } 56 - } 57 - ``` 58 53 59 - The login auth flow starts with a user identifier, which could be an atproto handle, DID, or a host URL. The high-level [StartAuthFlow()] method will resolve the identifier, send an auth request (PAR) to the server, persist request metadata in the [OAuthStore], and return a redirect URL for the user to visit: 54 + The login auth flow starts with a user identifier, which could be an atproto handle, DID, or an auth server URL (eg, a PDS). The high-level [StartAuthFlow()] method will resolve the identifier, send an auth request (PAR) to the server, persist request metadata in the [OAuthStore], and return a redirect URL for the user to visit: 60 55 61 - ``` 62 - http.HandleFunc("GET /oauth/login", HandleLogin) 56 + http.HandleFunc("GET /oauth/login", HandleLogin) 63 57 64 - func HandleLogin(w http.ResponseWriter, r *http.Request) { 65 - ctx := r.Context() 58 + func HandleLogin(w http.ResponseWriter, r *http.Request) { 59 + ctx := r.Context() 66 60 67 - // parse login identifier from the request 68 - identifier := "..." 61 + // parse login identifier from the request 62 + identifier := "..." 69 63 70 - redirectURL, err := oauthApp.StartAuthFlow(ctx, identifier) 71 - if err != nil { 72 - http.Error(w, err.Error(), http.StatusInternalServerError) 64 + redirectURL, err := oauthApp.StartAuthFlow(ctx, identifier) 65 + if err != nil { 66 + http.Error(w, err.Error(), http.StatusInternalServerError) 67 + } 68 + http.Redirect(w, r, redirectURL, http.StatusFound) 73 69 } 74 - http.Redirect(w, r, redirectURL, http.StatusFound) 75 - } 76 - ``` 77 70 78 71 The service then waits for a callback request on the configured endpoint. The [ProcessCallback()] method will load the earlier request metadata from the [OAuthStore], send an initial token request to the auth server, and validate that the session is consistent with the identifier from the begining of the login flow. 79 72 80 - ``` 81 - http.HandleFunc("GET /client-metadata.json", HandleClientMetadata) 73 + http.HandleFunc("GET /oauth/callback", HandleOAuthCallback) 82 74 83 - func HandleOAuthCallback(w http.ResponseWriter, r *http.Request) { 84 - ctx := r.Context() 75 + func HandleOAuthCallback(w http.ResponseWriter, r *http.Request) { 76 + ctx := r.Context() 85 77 86 - sessData, err := oauthApp.ProcessCallback(ctx, r.URL.Query()) 87 - if err != nil { 88 - http.Error(w, err.Error(), http.StatusInternalServerError) 89 - } 78 + sessData, err := oauthApp.ProcessCallback(ctx, r.URL.Query()) 79 + if err != nil { 80 + http.Error(w, err.Error(), http.StatusInternalServerError) 81 + } 90 82 91 - // web services might record the DID in a secure session cookie 92 - _ = sessData.AccountDID 83 + // web services might record the DID in a secure session cookie 84 + _ = sessData.AccountDID 93 85 94 - http.Redirect(w, r, "/app", http.StatusFound) 95 - } 96 - ``` 86 + http.Redirect(w, r, "/app", http.StatusFound) 87 + } 97 88 98 89 Finally, sessions can be resumed and used to make authenticated API calls to the user's host: 99 90 100 - ``` 101 - // web services might use a secure session cookie to determine user's DID for a request 102 - did := syntax.DID("did:plc:abc123") 91 + // web services might use a secure session cookie to determine user's DID for a request 92 + did := syntax.DID("did:plc:abc123") 103 93 104 - sess, err := oauthApp.ResumeSession(ctx, did) 105 - if err != nil { 106 - return err 107 - } 94 + sess, err := oauthApp.ResumeSession(ctx, did) 95 + if err != nil { 96 + return err 97 + } 108 98 109 - c := sess.APIClient() 99 + c := sess.APIClient() 110 100 111 - body := map[string]any{ 112 - "repo": *c.AccountDID, 113 - "collection": "app.bsky.feed.post", 114 - "record": map[string]any{ 115 - "$type": "app.bsky.feed.post", 116 - "text": "Hello World via OAuth!", 117 - "createdAt": syntax.DatetimeNow(), 118 - }, 119 - } 101 + body := map[string]any{ 102 + "repo": *c.AccountDID, 103 + "collection": "app.bsky.feed.post", 104 + "record": map[string]any{ 105 + "$type": "app.bsky.feed.post", 106 + "text": "Hello World via OAuth!", 107 + "createdAt": syntax.DatetimeNow(), 108 + }, 109 + } 120 110 121 - if err := c.Post(ctx, "com.atproto.repo.createRecord", body, nil); err != nil { 122 - return err 123 - } 124 - ``` 111 + if err := c.Post(ctx, "com.atproto.repo.createRecord", body, nil); err != nil { 112 + return err 113 + } 125 114 126 115 The [ClientSession] will handle nonce updates and token refreshes, and persist the results in the [OAuthStore]. 127 116