├── .gitignore
├── README.md
├── go.mod
├── go.sum
├── main.go
├── static
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── components
│ ├── Home.js
│ └── LoggedIn.js
│ ├── history.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── react-auth0-spa.js
│ ├── serviceWorker.js
│ └── setupTests.js
└── views
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 | static/src/auth_config.json
3 | # dependencies
4 | static/node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Authentication in Golang with JWTs
2 |
3 | 
4 |
5 | This application will show you how to build and secure a mock voting application. You'll create an API with Go and a frontend with React that will consume your API. You'll secure your API using Auth0 so that only authenticated users can access and vote on products.
6 |
7 | **🚀 Read the full tutorial here**:
8 |
9 | [Authentication in Golang with JWTs](https://auth0.com/blog/authentication-in-golang)
10 |
11 | ## Technology
12 |
13 | This demo uses:
14 |
15 | - [Go v1.14](https://golang.org/)
16 | - [React v16.13](https://reactjs.org/)
17 | - [Auth0](https://auth0.com/)
18 |
19 | Make sure you have [Go installed](https://golang.org/doc/install) before running the demo.
20 |
21 | ## Running the demo
22 |
23 | **Clone and get dependencies**
24 |
25 | * Clone the repo with `git clone https://github.com/auth0-blog/go-react-vr-auth`
26 | * Switch to the directory that holds the repo `cd go-react-vr-auth`
27 | * Run `go get` to grab the dependencies
28 |
29 | **Auth0 Setup**
30 |
31 | If you do not have an Auth0 account, [sign up](https://auth0.com/signup) for one now.
32 |
33 | * Navigate to the Auth0 [management dashboard](https://manage.auth0.com/) and click on **Create Application**. Name your application anything you'd like, select "Single Page Web Applications", and click **Create**.
34 | * Click on **Settings** and fill in **Allowed Callback URLs**, **Allowed Logout URLs**, and **Allowed Web Origins** with `http://localhost:3000` and then press **Save changes**.
35 | * Back in your code, create a new file to store some of these variables:
36 |
37 | ```bash
38 | touch static/src/auth_config.json
39 | ```
40 |
41 | Then fill it in with:
42 |
43 | ```json
44 | {
45 | "domain": "YOUR_DOMAIN",
46 | "clientId": "YOUR_CLIENT_ID"
47 | }
48 | ```
49 |
50 | Update this with your own credentials. You can find them in your Auth0 dashboard application page that you just created.
51 |
52 | * Now set up the Go API with Auth0. In the dashboard, click on **APIs** in the sidebar and then click **Create API**.
53 | * Give it any name you'd like. For identifier, this can be anything, but for naming conventions we recommend a URL format (doesn't have to be a real URL). Leave Signing Algorithm as is and click **Create**.
54 | * Update your `main.go` file with the required values from the Auth0 dashboard. There are 3 different spots that need to be updated:
55 |
56 | ```go
57 | // main.go
58 | // ...
59 | aud := "YOUR_API_IDENTIFIER"
60 | // ...
61 | iss := "https://YOUR_DOMAIN/"
62 | // ...
63 | resp, err := http.Get("https://YOUR_DOMAIN/.well-known/jwks.json")
64 | // ...
65 | ```
66 |
67 | Your **Identifier** is listed on the Settings tab of the API you just created in the dashboard. To find `YOUR_DOMAIN`, click on the **Quick Start** tab and scroll down to the code snippet. Copy the value for `options.Authority` and use that to replace `YOUR_DOMAIN`. Make sure you keep the trailing slash for `iss` in `main.go`.
68 |
69 | **Run React**
70 |
71 | * Open a new terminal tab and switch to the React directory and then pull in the required dependencies
72 |
73 | ```bash
74 | cd static
75 | npm install
76 | ```
77 |
78 | * Start your React application
79 |
80 | ```bash
81 | npm start
82 | ```
83 |
84 | **Run Go**
85 |
86 | * In a separate terminal tab, grab the dependencies
87 |
88 | ```bash
89 | go get
90 | ```
91 |
92 | * Build and run your Go application:
93 |
94 | ```bash
95 | go run .
96 | ```
97 |
98 | 💥 Navigate to [`http://localhost:3000`](http://localhost:3000) to see it in action! 💥
99 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/auth0-blog/go-vr-auth
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
8 | github.com/gorilla/handlers v1.4.2
9 | github.com/gorilla/mux v1.7.4
10 | github.com/rs/cors v1.7.0
11 | )
12 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw=
2 | github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=
3 | github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
4 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
5 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
6 | github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
7 | github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
8 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
9 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
10 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
11 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
12 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Import our dependencies. We'll use the standard HTTP library as well as the gorilla router for this app
4 | import (
5 | "encoding/json"
6 | "errors"
7 | "github.com/auth0/go-jwt-middleware"
8 | "github.com/dgrijalva/jwt-go"
9 | "github.com/gorilla/mux"
10 | "github.com/rs/cors"
11 | "net/http"
12 | )
13 |
14 | type Response struct {
15 | Message string `json:"message"`
16 | }
17 |
18 | type Jwks struct {
19 | Keys []JSONWebKeys `json:"keys"`
20 | }
21 |
22 | type JSONWebKeys struct {
23 | Kty string `json:"kty"`
24 | Kid string `json:"kid"`
25 | Use string `json:"use"`
26 | N string `json:"n"`
27 | E string `json:"e"`
28 | X5c []string `json:"x5c"`
29 | }
30 |
31 | /* We will first create a new type called Product
32 | This type will contain information about VR experiences */
33 | type Product struct {
34 | Id int
35 | Name string
36 | Slug string
37 | Description string
38 | }
39 |
40 | var products = []Product{
41 | Product{Id: 1, Name: "World of Authcraft", Slug: "world-of-authcraft", Description: "Battle bugs and protect yourself from invaders while you explore a scary world with no security"},
42 | Product{Id: 2, Name: "Ocean Explorer", Slug: "ocean-explorer", Description: "Explore the depths of the sea in this one of a kind underwater experience"},
43 | Product{Id: 3, Name: "Dinosaur Park", Slug: "dinosaur-park", Description: "Go back 65 million years in the past and ride a T-Rex"},
44 | Product{Id: 4, Name: "Cars VR", Slug: "cars-vr", Description: "Get behind the wheel of the fastest cars in the world."},
45 | Product{Id: 5, Name: "Robin Hood", Slug: "robin-hood", Description: "Pick up the bow and arrow and master the art of archery"},
46 | Product{Id: 6, Name: "Real World VR", Slug: "real-world-vr", Description: "Explore the seven wonders of the world in VR"},
47 | }
48 |
49 | func main() {
50 |
51 | jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
52 | ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
53 | // Verify 'aud' claim
54 | aud := "YOUR_API_IDENTIFIER"
55 | checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false)
56 | if !checkAud {
57 | return token, errors.New("Invalid audience.")
58 | }
59 | // Verify 'iss' claim
60 | iss := "https://YOUR_DOMAIN/"
61 | checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false)
62 | if !checkIss {
63 | return token, errors.New("Invalid issuer.")
64 | }
65 |
66 | cert, err := getPemCert(token)
67 | if err != nil {
68 | panic(err.Error())
69 | }
70 |
71 | result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
72 | return result, nil
73 | },
74 | SigningMethod: jwt.SigningMethodRS256,
75 | })
76 |
77 | r := mux.NewRouter()
78 |
79 | r.Handle("/", http.FileServer(http.Dir("./views/")))
80 | r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
81 |
82 | r.Handle("/products", jwtMiddleware.Handler(ProductsHandler)).Methods("GET")
83 | r.Handle("/products/{slug}/feedback", jwtMiddleware.Handler(AddFeedbackHandler)).Methods("POST")
84 |
85 | // For dev only - Set up CORS so React client can consume our API
86 | corsWrapper := cors.New(cors.Options{
87 | AllowedMethods: []string{"GET", "POST"},
88 | AllowedHeaders: []string{"Content-Type", "Origin", "Accept", "*"},
89 | })
90 |
91 | http.ListenAndServe(":8080", corsWrapper.Handler(r))
92 | }
93 |
94 | var ProductsHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
95 | payload, _ := json.Marshal(products)
96 |
97 | w.Header().Set("Content-Type", "application/json")
98 | w.Write([]byte(payload))
99 | })
100 |
101 | var AddFeedbackHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
102 | var product Product
103 | vars := mux.Vars(r)
104 | slug := vars["slug"]
105 |
106 | for _, p := range products {
107 | if p.Slug == slug {
108 | product = p
109 | }
110 | }
111 |
112 | w.Header().Set("Content-Type", "application/json")
113 | if product.Slug != "" {
114 | payload, _ := json.Marshal(product)
115 | w.Write([]byte(payload))
116 | } else {
117 | http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
118 | }
119 | })
120 |
121 | func getPemCert(token *jwt.Token) (string, error) {
122 | cert := ""
123 | resp, err := http.Get("https://YOUR-DOMAIN.com/.well-known/jwks.json")
124 |
125 | if err != nil {
126 | return cert, err
127 | }
128 | defer resp.Body.Close()
129 |
130 | var jwks = Jwks{}
131 | err = json.NewDecoder(resp.Body).Decode(&jwks)
132 |
133 | if err != nil {
134 | return cert, err
135 | }
136 |
137 | for k, _ := range jwks.Keys {
138 | if token.Header["kid"] == jwks.Keys[k].Kid {
139 | cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----"
140 | }
141 | }
142 |
143 | if cert == "" {
144 | err := errors.New("Unable to find appropriate key.")
145 | return cert, err
146 | }
147 |
148 | return cert, nil
149 | }
150 |
--------------------------------------------------------------------------------
/static/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/static/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "static",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@auth0/auth0-spa-js": "^1.8.1",
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.5.0",
9 | "@testing-library/user-event": "^7.2.1",
10 | "bootstrap": "^4.4.1",
11 | "react": "^16.13.1",
12 | "react-dom": "^16.13.1",
13 | "react-icons": "^3.10.0",
14 | "react-router-dom": "^5.1.2",
15 | "react-scripts": "3.4.1"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": "react-app"
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/static/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/auth0-blog/go-react-vr/a5d99b6bd369b3549e89602df1ff373715af5f17/static/public/favicon.ico
--------------------------------------------------------------------------------
/static/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Provide valuable feedback to VR experience developers.
12 | {!isAuthenticated && ( 13 | 14 | )} 15 |93 | Hi, {user.name}! Below you'll find the latest games that need 94 | feedback. Please provide honest feedback so developers can make the 95 | best games. 96 |
97 |