├── LICENSE ├── README.md ├── discord.go └── example └── main.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 Raven Ravener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord OAuth 2.0 2 | This is a provider for the package [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) implementing authentication endpoints for [Discord](https://discordapp.com) 3 | 4 | ## Install 5 | ```sh 6 | $ go get github.com/ravener/discord-oauth2 7 | ``` 8 | 9 | ## Usage 10 | ```go 11 | package main 12 | 13 | import ( 14 | "github.com/ravener/discord-oauth2" 15 | "golang.org/x/oauth2" 16 | ) 17 | 18 | func main() { 19 | conf := &oauth2.Config{ 20 | Endpoint: discord.Endpoint, 21 | Scopes: []string{discord.ScopeIdentify}, 22 | RedirectURL: "http://localhost:3000/auth/callback", 23 | ClientID: "id", 24 | ClientSecret: "secret", 25 | } 26 | // Use oauth2 package as normal, i.e 27 | // redirect users to conf.AuthCodeURL("state") for initial auth 28 | // then inside the callback: 29 | // - verify the state param as needed. 30 | // - exchange code with conf.Exchange(oauth2.NoContext, code) 31 | // - Store in session if necessary, etc. 32 | // to get like user's info use conf.Client(ctx, token) to get a proper http client 33 | // for such requests. 34 | } 35 | ``` 36 | A full authentication flow example server can be found in [example directory](example) 37 | 38 | You can join [`#oauth2` in my Discord Server](https://discord.gg/wpE3Nfp) for support and updates. 39 | 40 | ## License 41 | [MIT](LICENSE) 42 | -------------------------------------------------------------------------------- /discord.go: -------------------------------------------------------------------------------- 1 | package discord 2 | 3 | import ( 4 | "golang.org/x/oauth2" 5 | ) 6 | 7 | // All scope constants that can be used. 8 | const ( 9 | ScopeIdentify = "identify" 10 | ScopeBot = "bot" 11 | ScopeEmail = "email" 12 | ScopeGuilds = "guilds" 13 | ScopeGuildsJoin = "guilds.join" 14 | ScopeGuildsMembersRead = "guilds.members.read" 15 | ScopeConnections = "connections" 16 | ScopeGroupDMJoin = "gdm.join" 17 | ScopeMessagesRead = "messages.read" 18 | ScopeRPC = "rpc" // Whitelist only 19 | ScopeRPCAPI = "rpc.api" // Whitelist only 20 | ScopeRPCNotificationsRead = "rpc.notifications.read" // Whitelist only 21 | ScopeWebhookIncoming = "webhook.Incoming" 22 | ScopeApplicationsBuildsUpload = "applications.builds.upload" // Whitelist only 23 | ScopeApplicationsBuildsRead = "applications.builds.read" 24 | ScopeApplicationsStoreUpdate = "applications.store.update" 25 | ScopeApplicationsEntitlements = "applications.entitlements" 26 | ScopeRelationshipsRead = "relationships.read" // Whitelist only 27 | ScopeActivitiesRead = "activities.read" // Whitelist only 28 | ScopeActivitiesWrite = "activities.write" // Whitelist only 29 | ScopeApplicationsCommands = "applications.commands" 30 | ScopeApplicationsCommandsUpdate = "applications.commands.update" 31 | ) 32 | 33 | // Endpoint is Discord's OAuth 2.0 endpoint. 34 | var Endpoint = oauth2.Endpoint{ 35 | AuthURL: "https://discord.com/api/oauth2/authorize", 36 | TokenURL: "https://discord.com/api/oauth2/token", 37 | AuthStyle: oauth2.AuthStyleInParams, 38 | } 39 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | // Extremely barebones server to demonstrate OAuth 2.0 flow with Discord 2 | // Uses native net/http to be dependency-less and easy to run. 3 | // No sessions logic implemented, re-login needed each visit. 4 | // Edit the config lines a little bit then go build/run it as normal. 5 | package main 6 | 7 | import ( 8 | "github.com/ravener/discord-oauth2" 9 | "golang.org/x/oauth2" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "context" 14 | ) 15 | 16 | // This is the state key used for security, sent in login, validated in callback. 17 | // For this example we keep it simple and hardcode a string 18 | // but in real apps you must provide a proper function that generates a state. 19 | var state = "random" 20 | 21 | func main() { 22 | // Create a config. 23 | // Ensure you add the redirect url in the application's oauth2 settings 24 | // in the discord devs page. 25 | conf := &oauth2.Config{ 26 | RedirectURL: "http://localhost:3000/auth/callback", 27 | // This next 2 lines must be edited before running this. 28 | ClientID: "id", 29 | ClientSecret: "secret", 30 | Scopes: []string{discord.ScopeIdentify}, 31 | Endpoint: discord.Endpoint, 32 | } 33 | 34 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 35 | // Step 1: Redirect to the OAuth 2.0 Authorization page. 36 | // This route could be named /login etc 37 | http.Redirect(w, r, conf.AuthCodeURL(state), http.StatusTemporaryRedirect) 38 | }) 39 | 40 | // Step 2: After user authenticates their accounts this callback is fired. 41 | // the state we sent in login is also sent back to us here 42 | // we have to verify it as necessary before continuing. 43 | http.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) { 44 | if r.FormValue("state") != state { 45 | w.WriteHeader(http.StatusBadRequest) 46 | w.Write([]byte("State does not match.")) 47 | return 48 | } 49 | // Step 3: We exchange the code we got for an access token 50 | // Then we can use the access token to do actions, limited to scopes we requested 51 | token, err := conf.Exchange(context.Background(), r.FormValue("code")) 52 | 53 | if err != nil { 54 | w.WriteHeader(http.StatusInternalServerError) 55 | w.Write([]byte(err.Error())) 56 | return 57 | } 58 | 59 | // Step 4: Use the access token, here we use it to get the logged in user's info. 60 | res, err := conf.Client(context.Background(), token).Get("https://discord.com/api/users/@me") 61 | 62 | if err != nil || res.StatusCode != 200 { 63 | w.WriteHeader(http.StatusInternalServerError) 64 | if err != nil { 65 | w.Write([]byte(err.Error())) 66 | } else { 67 | w.Write([]byte(res.Status)) 68 | } 69 | return 70 | } 71 | 72 | defer res.Body.Close() 73 | 74 | body, err := ioutil.ReadAll(res.Body) 75 | 76 | if err != nil { 77 | w.WriteHeader(http.StatusInternalServerError) 78 | w.Write([]byte(err.Error())) 79 | return 80 | } 81 | 82 | w.Write(body) 83 | }) 84 | 85 | log.Println("Listening on :3000") 86 | log.Fatal(http.ListenAndServe(":3000", nil)) 87 | } 88 | --------------------------------------------------------------------------------