├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── basic.go ├── basic_test.go ├── coverage.txt ├── middleware.go ├── middleware_test.go ├── model.go ├── security.go ├── security_test.go ├── server.go ├── server_test.go └── test ├── authserver └── main.go └── resourceserver └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | .idea 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.12 5 | 6 | before_install: 7 | - go get -t -v ./... 8 | 9 | script: 10 | - go test -coverprofile=coverage.txt -covermode=atomic 11 | 12 | after_success: 13 | - bash <(curl -s https://codecov.io/bash) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Massimo Zerbini 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 | # oauth middleware 2 | OAuth 2.0 Authorization Server & Authorization Middleware for [Gin-Gonic](https://github.com/gin-gonic/gin) 3 | 4 | This library offers an OAuth 2.0 Authorization Server based on Gin-Gonic and an Authorization Middleware usable in Resource Servers developed with Gin-Gonic. 5 | 6 | 7 | ## Build status 8 | [![Build Status](https://travis-ci.org/maxzerbini/oauth.svg?branch=master)](https://travis-ci.org/maxzerbini/oauth) 9 | 10 | ## Authorization Server 11 | The Authorization Server is implemented by the struct _OAuthBearerServer_ that manages two grant types of authorizations (password and client_credentials). 12 | This Authorization Server is made to provide an authorization token usable for consuming resources API. 13 | 14 | ### Password grant type 15 | _OAuthBearerServer_ supports the password grant type, allowing the token generation for username / password credentials. 16 | 17 | ### Client Credentials grant type 18 | _OAuthBearerServer_ supports the client_credentials grant type, allowing the token generation for client_id / client_secret credentials. 19 | 20 | ### Authorization Code and Implicit grant type 21 | These grant types are currently partially supported implementing AuthorizationCodeVerifier interface. The method ValidateCode is called during the phase two of the authorization_code grant type evalutations. 22 | 23 | ### Refresh token grant type 24 | If authorization token will expire, the client can regenerate the token calling the authorization server and using the refresh_token grant type. 25 | 26 | ## Authorization Middleware 27 | The Gin-Gonic middleware _BearerAuthentication_ intercepts the resource server calls and authorizes only resource requests containing a valid bearer token. 28 | 29 | ## Token Formatter 30 | Authorization Server crypts the token using the Token Formatter and Authorization Middleware decrypts the token using the same Token Formatter. 31 | This library contains a default implementation of the formatter interface called _SHA256RC4TokenSecureFormatter_ based on the algorithms SHA256 and RC4. 32 | Programmers can develop their Token Formatter implementing the interface _TokenSecureFormatter_ and this is really recommended before publishing the API in a production environment. 33 | 34 | ## Credentials Verifier 35 | The interface _CredentialsVerifier_ defines the hooks called during the token generation process. 36 | The methods are called in this order: 37 | - _ValidateUser() or ValidateClient()_ called first for credentials verification 38 | - _AddClaims()_ used for add information to the token that will be encrypted 39 | - _StoreTokenId()_ called after the token generation but before the response, programmers can use this method for storing the generated Ids 40 | - _AddProperties()_ used for add clear information to the response 41 | 42 | There is another method in the _CredentialsVerifier_ interface that is involved during the refresh token process. 43 | In this case the methods are called in this order: 44 | - _ValidateTokenId()_ called first for TokenId verification, the method receives the TokenId related to the token associated to the refresh token 45 | - _AddClaims()_ used for add information to the token that will be encrypted 46 | - _StoreTokenId()_ called after the token regeneration but before the response, programmers can use this method for storing the generated Ids 47 | - _AddProperties()_ used for add clear information to the response 48 | 49 | ## Authorization Server usage example 50 | This snippet shows how to create an authorization server 51 | ```Go 52 | func main() { 53 | router := gin.New() 54 | router.Use(gin.Recovery()) 55 | router.Use(gin.Logger()) 56 | 57 | s := oauth.NewOAuthBearerServer( 58 | "mySecretKey-10101", 59 | time.Second*120, 60 | &TestUserVerifier{}, 61 | nil) 62 | router.POST("/token", s.UserCredentials) 63 | router.POST("/auth", s.ClientCredentials) 64 | 65 | router.Run(":9090") 66 | } 67 | ``` 68 | See [/test/authserver/main.go](https://github.com/maxzerbini/oauth/blob/master/test/authserver/main.go) for the full example. 69 | 70 | ## Authorization Middleware usage example 71 | This snippet shows how to use the middleware 72 | ```Go 73 | authorized := router.Group("/") 74 | // use the Bearer Athentication middleware 75 | authorized.Use(oauth.Authorize("mySecretKey-10101", nil)) 76 | 77 | authorized.GET("/customers", GetCustomers) 78 | authorized.GET("/customers/:id/orders", GetOrders) 79 | ``` 80 | See [/test/resourceserver/main.go](https://github.com/maxzerbini/oauth/blob/master/test/resourceserver/main.go) for the full example. 81 | 82 | Note that the authorization server and the authorization middleware are both using the same token formatter and the same secret key for encryption/decryption. 83 | 84 | ## Note 85 | This master branch introduces breaking changes in the interface CredentialsVerifier methods _ValidateUser_, _ValidateClient_ and _AddClaims_. Refer to v1 branch for the previous implementation. 86 | Updated server implementation in v3 due to go.uuid library change. 87 | 88 | ## Reference 89 | - [OAuth 2.0 RFC](https://tools.ietf.org/html/rfc6749) 90 | - [OAuth 2.0 Bearer Token Usage RFC](https://tools.ietf.org/html/rfc6750) 91 | 92 | ## License 93 | [MIT](https://github.com/maxzerbini/oauth/blob/master/LICENSE) 94 | -------------------------------------------------------------------------------- /basic.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import "github.com/gin-gonic/gin" 4 | import "strings" 5 | import "encoding/base64" 6 | import "errors" 7 | 8 | // GetBasicAuthentication get username and password from Authorization header 9 | func GetBasicAuthentication(ctx *gin.Context) (username, password string, err error) { 10 | if header := ctx.Request.Header.Get("Authorization"); header != "" { 11 | if strings.ToLower(header[:6]) == "basic " { 12 | // decode header value 13 | value, err := base64.StdEncoding.DecodeString(header[6:]) 14 | if err != nil { 15 | return "", "", err 16 | } 17 | strValue := string(value) 18 | if ind := strings.Index(strValue, ":"); ind > 0 { 19 | return strValue[:ind], strValue[ind+1:], nil 20 | } 21 | } 22 | } 23 | return "", "", nil 24 | } 25 | 26 | // Check Basic Autrhorization header credentials 27 | func CheckBasicAuthentication(username, password string, ctx *gin.Context) error { 28 | u, p, err := GetBasicAuthentication(ctx) 29 | if err != nil { 30 | return err 31 | } else { 32 | if u != "" && p != "" { 33 | if u != username && p != password { 34 | return errors.New("Invalid credentials") 35 | } 36 | } 37 | return nil 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /basic_test.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func TestGetBasicAuthentication(t *testing.T) { 12 | gin.SetMode(gin.TestMode) 13 | 14 | req, _ := http.NewRequest("GET", "/token", nil) 15 | req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password123456"))) 16 | 17 | context := &gin.Context{Request: req} 18 | 19 | username, password, err := GetBasicAuthentication(context) 20 | if err != nil { 21 | t.Fatalf("Error %s", err.Error()) 22 | } else { 23 | if username != "admin" { 24 | t.Fatalf("Wrong Username = %s", username) 25 | } 26 | if password != "password123456" { 27 | t.Fatalf("Wrong Username = %s", password) 28 | } 29 | } 30 | } 31 | 32 | func TestVoidBasicAuthentication(t *testing.T) { 33 | gin.SetMode(gin.TestMode) 34 | 35 | req, _ := http.NewRequest("GET", "/token", nil) 36 | 37 | context := &gin.Context{Request: req} 38 | 39 | username, password, err := GetBasicAuthentication(context) 40 | if err != nil { 41 | t.Fatalf("Error %s", err.Error()) 42 | } else { 43 | if username != "" { 44 | t.Fatalf("Wrong Username = %s", username) 45 | } 46 | if password != "" { 47 | t.Fatalf("Wrong Username = %s", password) 48 | } 49 | } 50 | 51 | } 52 | 53 | func TestCheckBasicAuthentication(t *testing.T) { 54 | gin.SetMode(gin.TestMode) 55 | 56 | req, _ := http.NewRequest("GET", "/token", nil) 57 | req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password123456"))) 58 | 59 | context := &gin.Context{Request: req} 60 | 61 | err := CheckBasicAuthentication("admin", "password123456", context) 62 | if err != nil { 63 | t.Fatalf("Error %s", err.Error()) 64 | } else { 65 | t.Log("Credentials are OK") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /coverage.txt: -------------------------------------------------------------------------------- 1 | mode: atomic 2 | github.com/maxzerbini/oauth/server.go:51.53,52.22 1 1 3 | github.com/maxzerbini/oauth/server.go:55.2,60.12 2 1 4 | github.com/maxzerbini/oauth/server.go:52.22,54.3 1 1 5 | github.com/maxzerbini/oauth/server.go:64.63,70.38 5 0 6 | github.com/maxzerbini/oauth/server.go:80.2,82.22 3 0 7 | github.com/maxzerbini/oauth/server.go:70.38,74.17 3 0 8 | github.com/maxzerbini/oauth/server.go:74.17,77.4 2 0 9 | github.com/maxzerbini/oauth/server.go:86.65,91.42 4 0 10 | github.com/maxzerbini/oauth/server.go:100.2,104.22 4 0 11 | github.com/maxzerbini/oauth/server.go:91.42,95.17 3 0 12 | github.com/maxzerbini/oauth/server.go:95.17,98.4 2 0 13 | github.com/maxzerbini/oauth/server.go:108.65,116.20 7 0 14 | github.com/maxzerbini/oauth/server.go:125.2,126.24 2 0 15 | github.com/maxzerbini/oauth/server.go:116.20,120.17 3 0 16 | github.com/maxzerbini/oauth/server.go:120.17,123.4 2 0 17 | github.com/maxzerbini/oauth/server.go:130.159,132.29 1 6 18 | github.com/maxzerbini/oauth/server.go:132.29,134.17 2 3 19 | github.com/maxzerbini/oauth/server.go:134.17,136.18 2 2 20 | github.com/maxzerbini/oauth/server.go:136.18,139.19 2 2 21 | github.com/maxzerbini/oauth/server.go:142.5,143.19 2 2 22 | github.com/maxzerbini/oauth/server.go:139.19,141.6 1 0 23 | github.com/maxzerbini/oauth/server.go:143.19,145.6 1 2 24 | github.com/maxzerbini/oauth/server.go:145.11,147.6 1 0 25 | github.com/maxzerbini/oauth/server.go:148.10,150.5 1 0 26 | github.com/maxzerbini/oauth/server.go:152.9,155.4 1 1 27 | github.com/maxzerbini/oauth/server.go:156.8,156.46 1 3 28 | github.com/maxzerbini/oauth/server.go:156.46,158.17 2 2 29 | github.com/maxzerbini/oauth/server.go:158.17,160.18 2 2 30 | github.com/maxzerbini/oauth/server.go:160.18,163.19 2 2 31 | github.com/maxzerbini/oauth/server.go:166.5,167.19 2 2 32 | github.com/maxzerbini/oauth/server.go:163.19,165.6 1 0 33 | github.com/maxzerbini/oauth/server.go:167.19,169.6 1 2 34 | github.com/maxzerbini/oauth/server.go:169.11,171.6 1 0 35 | github.com/maxzerbini/oauth/server.go:172.10,174.5 1 0 36 | github.com/maxzerbini/oauth/server.go:175.9,178.4 1 0 37 | github.com/maxzerbini/oauth/server.go:179.8,179.46 1 1 38 | github.com/maxzerbini/oauth/server.go:179.46,180.69 1 0 39 | github.com/maxzerbini/oauth/server.go:180.69,182.18 2 0 40 | github.com/maxzerbini/oauth/server.go:182.18,184.19 2 0 41 | github.com/maxzerbini/oauth/server.go:184.19,187.20 2 0 42 | github.com/maxzerbini/oauth/server.go:190.6,191.20 2 0 43 | github.com/maxzerbini/oauth/server.go:187.20,189.7 1 0 44 | github.com/maxzerbini/oauth/server.go:191.20,193.7 1 0 45 | github.com/maxzerbini/oauth/server.go:193.12,195.7 1 0 46 | github.com/maxzerbini/oauth/server.go:196.11,198.6 1 0 47 | github.com/maxzerbini/oauth/server.go:199.10,202.5 1 0 48 | github.com/maxzerbini/oauth/server.go:203.9,206.4 1 0 49 | github.com/maxzerbini/oauth/server.go:207.8,207.41 1 1 50 | github.com/maxzerbini/oauth/server.go:207.41,210.17 2 1 51 | github.com/maxzerbini/oauth/server.go:210.17,212.18 2 1 52 | github.com/maxzerbini/oauth/server.go:212.18,215.19 2 1 53 | github.com/maxzerbini/oauth/server.go:215.19,218.20 2 1 54 | github.com/maxzerbini/oauth/server.go:221.6,222.20 2 1 55 | github.com/maxzerbini/oauth/server.go:218.20,220.7 1 0 56 | github.com/maxzerbini/oauth/server.go:222.20,224.7 1 1 57 | github.com/maxzerbini/oauth/server.go:224.12,226.7 1 0 58 | github.com/maxzerbini/oauth/server.go:227.11,229.6 1 0 59 | github.com/maxzerbini/oauth/server.go:230.10,233.5 1 0 60 | github.com/maxzerbini/oauth/server.go:234.9,237.4 1 0 61 | github.com/maxzerbini/oauth/server.go:238.8,241.3 1 0 62 | github.com/maxzerbini/oauth/server.go:244.128,248.23 3 8 63 | github.com/maxzerbini/oauth/server.go:258.2,260.28 2 8 64 | github.com/maxzerbini/oauth/server.go:248.23,250.17 2 8 65 | github.com/maxzerbini/oauth/server.go:250.17,252.4 1 8 66 | github.com/maxzerbini/oauth/server.go:252.9,255.4 1 0 67 | github.com/maxzerbini/oauth/server.go:263.111,265.16 2 7 68 | github.com/maxzerbini/oauth/server.go:268.2,269.16 2 7 69 | github.com/maxzerbini/oauth/server.go:272.2,274.23 2 7 70 | github.com/maxzerbini/oauth/server.go:281.2,281.18 1 7 71 | github.com/maxzerbini/oauth/server.go:265.16,267.3 1 0 72 | github.com/maxzerbini/oauth/server.go:269.16,271.3 1 0 73 | github.com/maxzerbini/oauth/server.go:274.23,277.17 2 7 74 | github.com/maxzerbini/oauth/server.go:277.17,279.4 1 7 75 | github.com/maxzerbini/oauth/basic.go:9.86,10.69 1 3 76 | github.com/maxzerbini/oauth/basic.go:23.2,23.20 1 1 77 | github.com/maxzerbini/oauth/basic.go:10.69,11.46 1 2 78 | github.com/maxzerbini/oauth/basic.go:11.46,14.18 2 2 79 | github.com/maxzerbini/oauth/basic.go:17.4,18.52 2 2 80 | github.com/maxzerbini/oauth/basic.go:14.18,16.5 1 0 81 | github.com/maxzerbini/oauth/basic.go:18.52,20.5 1 2 82 | github.com/maxzerbini/oauth/basic.go:27.82,29.16 2 1 83 | github.com/maxzerbini/oauth/basic.go:29.16,31.3 1 0 84 | github.com/maxzerbini/oauth/basic.go:31.8,32.25 1 1 85 | github.com/maxzerbini/oauth/basic.go:37.3,37.13 1 1 86 | github.com/maxzerbini/oauth/basic.go:32.25,33.38 1 1 87 | github.com/maxzerbini/oauth/basic.go:33.38,35.5 1 0 88 | github.com/maxzerbini/oauth/middleware.go:19.102,21.22 2 1 89 | github.com/maxzerbini/oauth/middleware.go:24.2,25.11 2 1 90 | github.com/maxzerbini/oauth/middleware.go:21.22,23.3 1 1 91 | github.com/maxzerbini/oauth/middleware.go:30.82,32.2 1 0 92 | github.com/maxzerbini/oauth/middleware.go:37.61,40.16 3 0 93 | github.com/maxzerbini/oauth/middleware.go:40.16,43.3 2 0 94 | github.com/maxzerbini/oauth/middleware.go:43.8,50.3 6 0 95 | github.com/maxzerbini/oauth/middleware.go:54.93,55.19 1 2 96 | github.com/maxzerbini/oauth/middleware.go:58.2,59.26 2 2 97 | github.com/maxzerbini/oauth/middleware.go:62.2,63.16 2 2 98 | github.com/maxzerbini/oauth/middleware.go:66.2,66.69 1 2 99 | github.com/maxzerbini/oauth/middleware.go:69.2,69.19 1 1 100 | github.com/maxzerbini/oauth/middleware.go:55.19,57.3 1 0 101 | github.com/maxzerbini/oauth/middleware.go:59.26,61.3 1 0 102 | github.com/maxzerbini/oauth/middleware.go:63.16,65.3 1 0 103 | github.com/maxzerbini/oauth/middleware.go:66.69,68.3 1 1 104 | github.com/maxzerbini/oauth/security.go:20.70,22.2 1 5 105 | github.com/maxzerbini/oauth/security.go:24.73,26.16 2 7 106 | github.com/maxzerbini/oauth/security.go:29.2,29.25 1 7 107 | github.com/maxzerbini/oauth/security.go:26.16,28.3 1 0 108 | github.com/maxzerbini/oauth/security.go:32.87,34.16 2 7 109 | github.com/maxzerbini/oauth/security.go:37.2,37.25 1 7 110 | github.com/maxzerbini/oauth/security.go:34.16,36.3 1 0 111 | github.com/maxzerbini/oauth/security.go:40.75,42.16 2 2 112 | github.com/maxzerbini/oauth/security.go:45.2,46.16 2 2 113 | github.com/maxzerbini/oauth/security.go:49.2,49.15 1 2 114 | github.com/maxzerbini/oauth/security.go:42.16,44.3 1 0 115 | github.com/maxzerbini/oauth/security.go:46.16,48.3 1 0 116 | github.com/maxzerbini/oauth/security.go:52.103,54.16 2 2 117 | github.com/maxzerbini/oauth/security.go:57.2,58.16 2 2 118 | github.com/maxzerbini/oauth/security.go:61.2,61.21 1 2 119 | github.com/maxzerbini/oauth/security.go:54.16,56.3 1 0 120 | github.com/maxzerbini/oauth/security.go:58.16,60.3 1 0 121 | github.com/maxzerbini/oauth/security.go:64.62,66.16 2 19 122 | github.com/maxzerbini/oauth/security.go:69.2,69.55 1 19 123 | github.com/maxzerbini/oauth/security.go:66.16,68.3 1 0 124 | github.com/maxzerbini/oauth/security.go:72.64,74.16 2 7 125 | github.com/maxzerbini/oauth/security.go:77.2,77.43 1 7 126 | github.com/maxzerbini/oauth/security.go:74.16,76.3 1 0 127 | github.com/maxzerbini/oauth/security.go:85.71,88.2 2 1 128 | github.com/maxzerbini/oauth/security.go:90.78,93.16 3 2 129 | github.com/maxzerbini/oauth/security.go:96.2,97.18 2 2 130 | github.com/maxzerbini/oauth/security.go:93.16,95.3 1 0 131 | github.com/maxzerbini/oauth/security.go:100.80,103.16 3 1 132 | github.com/maxzerbini/oauth/security.go:106.2,107.18 2 1 133 | github.com/maxzerbini/oauth/security.go:103.16,104.13 1 0 134 | github.com/maxzerbini/oauth/security.go:115.83,118.2 2 4 135 | github.com/maxzerbini/oauth/security.go:120.84,127.16 7 17 136 | github.com/maxzerbini/oauth/security.go:130.2,131.18 2 17 137 | github.com/maxzerbini/oauth/security.go:127.16,129.3 1 0 138 | github.com/maxzerbini/oauth/security.go:134.86,135.22 1 6 139 | github.com/maxzerbini/oauth/security.go:138.2,140.16 3 6 140 | github.com/maxzerbini/oauth/security.go:143.2,147.25 5 6 141 | github.com/maxzerbini/oauth/security.go:152.2,152.23 1 6 142 | github.com/maxzerbini/oauth/security.go:135.22,137.3 1 0 143 | github.com/maxzerbini/oauth/security.go:140.16,142.3 1 0 144 | github.com/maxzerbini/oauth/security.go:147.25,148.19 1 192 145 | github.com/maxzerbini/oauth/security.go:148.19,150.4 1 0 146 | -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // BearerAuthentication middleware for Gin-Gonic 13 | type BearerAuthentication struct { 14 | secretKey string 15 | provider *TokenProvider 16 | } 17 | 18 | // NewBearerAuthentication create a BearerAuthentication middleware 19 | func NewBearerAuthentication(secretKey string, formatter TokenSecureFormatter) *BearerAuthentication { 20 | ba := &BearerAuthentication{secretKey: secretKey} 21 | if formatter == nil { 22 | formatter = NewSHA256RC4TokenSecurityProvider([]byte(secretKey)) 23 | } 24 | ba.provider = NewTokenProvider(formatter) 25 | return ba 26 | } 27 | 28 | // Authorize is the OAuth 2.0 middleware for Gin-Gonic resource server. 29 | // Authorize creates a BearerAuthentication middlever and return the Authorize method. 30 | func Authorize(secretKey string, formatter TokenSecureFormatter) gin.HandlerFunc { 31 | return NewBearerAuthentication(secretKey, nil).Authorize 32 | } 33 | 34 | // Authorize verifies the bearer token authorizing or not the request. 35 | // Token is retreived from the Authorization HTTP header that respects the format 36 | // Authorization: Bearer {access_token} 37 | func (ba *BearerAuthentication) Authorize(ctx *gin.Context) { 38 | auth := ctx.Request.Header.Get("Authorization") 39 | token, err := ba.checkAuthorizationHeader(auth) 40 | if err != nil { 41 | ctx.JSON(http.StatusUnauthorized, "Not authorized: "+err.Error()) 42 | ctx.AbortWithStatus(401) 43 | } else { 44 | ctx.Set("oauth.credential", token.Credential) 45 | ctx.Set("oauth.claims", token.Claims) 46 | ctx.Set("oauth.scope", token.Scope) 47 | ctx.Set("oauth.tokentype", token.TokenType) 48 | ctx.Set("oauth.accesstoken", auth[7:]) 49 | ctx.Next() 50 | } 51 | } 52 | 53 | // Check header and token. 54 | func (ba *BearerAuthentication) checkAuthorizationHeader(auth string) (t *Token, err error) { 55 | if len(auth) < 7 { 56 | return nil, errors.New("Invalid bearer authorization header") 57 | } 58 | authType := strings.ToLower(auth[:6]) 59 | if authType != "bearer" { 60 | return nil, errors.New("Invalid bearer authorization header") 61 | } 62 | token, err := ba.provider.DecryptToken(auth[7:]) 63 | if err != nil { 64 | return nil, errors.New("Invalid token") 65 | } 66 | if time.Now().UTC().After(token.CreationDate.Add(token.ExperesIn)) { 67 | return nil, errors.New("Token expired") 68 | } 69 | return token, nil 70 | } 71 | -------------------------------------------------------------------------------- /middleware_test.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var _mut *BearerAuthentication 8 | 9 | func init() { 10 | _mut = NewBearerAuthentication( 11 | "mySecretKey-10101", 12 | nil) 13 | } 14 | 15 | func TestAuthorizationHeader(t *testing.T) { 16 | code, resp := _sut.generateTokenResponse("password", "user111", "password111", "", "", "", "", nil) 17 | if code != 200 { 18 | t.Fatalf("Error StatusCode = %d", code) 19 | } 20 | t.Logf("Token response: %v", resp) 21 | 22 | header := "Bearer " + resp.(*TokenResponse).Token 23 | token, err := _mut.checkAuthorizationHeader(header) 24 | if err != nil { 25 | t.Fatalf("Error %s", err.Error()) 26 | } 27 | t.Logf("Verified token : %v", token) 28 | } 29 | 30 | func TestExpiredAuthorizationHeader(t *testing.T) { 31 | header := `Bearer wMFZSkQ1kSTbQ9mkHufsfeHCnKo05TSEyLyjSiKOafAUQv7s0NClIgBQSDGKoRzeWfB2G0bKO7EE3P9MnaZNxkx2CtWVfTJkCXsIpo2eyF8Nw+ub5nr4Bxmj6JeOumQMrFogBHMnMT7Em7EhqQO+CICQ3cVX5suqsVkEZ/gkXfjKnnEH6qKYz3S3IN/ry3pVGaQc1wAn/cYqPA1SD+CAYqkriWgIGWJmYv3W9eRSoEWgfgigdM6kmZvlDxTlrACLOvzA/JCXK7qnP8TuFz4yAtNmBoNVw0PTjxIdBFJEC7RdZyQcO3SdgGykxgPqGhiW3Z4F7ZG3mzmy/SoSJIPnmmFIreDWt6+QOsUyeHkEu74G` 32 | _, err := _mut.checkAuthorizationHeader(header) 33 | if err == nil { 34 | t.Fatalf("Error %s", err.Error()) 35 | } 36 | t.Logf("Error : %v", err) 37 | } 38 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // TokenResponse is the authorization server response 8 | type TokenResponse struct { 9 | Token string `json:"access_token"` 10 | RefreshToken string `json:"refresh_token"` 11 | TokenType string `json:"token_type"` // bearer 12 | ExperesIn int64 `json:"expires_in"` // secs 13 | Properties map[string]string `json:"properties"` 14 | } 15 | 16 | // Token structure generated by the authorization server 17 | type Token struct { 18 | Id string `json:"id_token"` 19 | CreationDate time.Time `json:"date"` 20 | ExperesIn time.Duration `json:"expires_in"` // secs 21 | Credential string `json:"credential"` 22 | Scope string `json:"scope"` 23 | Claims map[string]string `json:"claims"` 24 | TokenType string `json:"type"` // "U" for user, "C" for client 25 | } 26 | 27 | // RefreshToken structure included in the authorization server response 28 | type RefreshToken struct { 29 | CreationDate time.Time `json:"date"` 30 | TokenId string `json:"id_token"` 31 | RefreshTokenId string `json:"id_refresh_token"` 32 | Credential string `json:"credential"` 33 | TokenType string `json:"type"` // "U" for user, "C" for client 34 | Scope string `json:"scope"` 35 | } 36 | -------------------------------------------------------------------------------- /security.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "crypto/rc4" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "encoding/json" 8 | "errors" 9 | ) 10 | 11 | type TokenSecureFormatter interface { 12 | CryptToken(source []byte) ([]byte, error) 13 | DecryptToken(source []byte) ([]byte, error) 14 | } 15 | 16 | type TokenProvider struct { 17 | secureFormatter TokenSecureFormatter 18 | } 19 | 20 | func NewTokenProvider(formatter TokenSecureFormatter) *TokenProvider { 21 | return &TokenProvider{secureFormatter: formatter} 22 | } 23 | 24 | func (tp *TokenProvider) CryptToken(t *Token) (token string, err error) { 25 | bToken, err := json.Marshal(t) 26 | if err != nil { 27 | return "", err 28 | } 29 | return tp.crypt(bToken) 30 | } 31 | 32 | func (tp *TokenProvider) CryptRefreshToken(t *RefreshToken) (token string, err error) { 33 | bToken, err := json.Marshal(t) 34 | if err != nil { 35 | return "", err 36 | } 37 | return tp.crypt(bToken) 38 | } 39 | 40 | func (tp *TokenProvider) DecryptToken(token string) (t *Token, err error) { 41 | bToken, err := tp.decrypt(token) 42 | if err != nil { 43 | return nil, err 44 | } 45 | err = json.Unmarshal(bToken, &t) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return t, nil 50 | } 51 | 52 | func (tp *TokenProvider) DecryptRefreshTokens(refreshToken string) (refresh *RefreshToken, err error) { 53 | bRefresh, err := tp.decrypt(refreshToken) 54 | if err != nil { 55 | return nil, err 56 | } 57 | err = json.Unmarshal(bRefresh, &refresh) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return refresh, nil 62 | } 63 | 64 | func (tp *TokenProvider) crypt(token []byte) (string, error) { 65 | ctoken, err := tp.secureFormatter.CryptToken(token) 66 | if err != nil { 67 | return "", err 68 | } 69 | return base64.StdEncoding.EncodeToString(ctoken), nil 70 | } 71 | 72 | func (tp *TokenProvider) decrypt(token string) ([]byte, error) { 73 | b, err := base64.StdEncoding.DecodeString(token) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return tp.secureFormatter.DecryptToken(b) 78 | } 79 | 80 | type RC4TokenSecureFormatter struct { 81 | key []byte 82 | cipher *rc4.Cipher 83 | } 84 | 85 | func NewRC4TokenSecurityProvider(key []byte) *RC4TokenSecureFormatter { 86 | var sc = &RC4TokenSecureFormatter{key: key} 87 | return sc 88 | } 89 | 90 | func (sc *RC4TokenSecureFormatter) CryptToken(source []byte) ([]byte, error) { 91 | dest := make([]byte, len(source)) 92 | cipher, err := rc4.NewCipher(sc.key) 93 | if err != nil { 94 | return nil, err 95 | } 96 | cipher.XORKeyStream(dest, source) 97 | return dest, nil 98 | } 99 | 100 | func (sc *RC4TokenSecureFormatter) DecryptToken(source []byte) ([]byte, error) { 101 | dest := make([]byte, len(source)) 102 | cipher, err := rc4.NewCipher(sc.key) 103 | if err != nil { 104 | panic(err) 105 | } 106 | cipher.XORKeyStream(dest, source) 107 | return dest, nil 108 | } 109 | 110 | type SHA256RC4TokenSecureFormatter struct { 111 | key []byte 112 | cipher *rc4.Cipher 113 | } 114 | 115 | func NewSHA256RC4TokenSecurityProvider(key []byte) *SHA256RC4TokenSecureFormatter { 116 | var sc = &SHA256RC4TokenSecureFormatter{key: key} 117 | return sc 118 | } 119 | 120 | func (sc *SHA256RC4TokenSecureFormatter) CryptToken(source []byte) ([]byte, error) { 121 | hasher := sha256.New() 122 | hasher.Write(source) 123 | hash := hasher.Sum(nil) 124 | newSource := append(hash, source...) 125 | dest := make([]byte, len(newSource)) 126 | cipher, err := rc4.NewCipher(sc.key) 127 | if err != nil { 128 | return nil, err 129 | } 130 | cipher.XORKeyStream(dest, newSource) 131 | return dest, nil 132 | } 133 | 134 | func (sc *SHA256RC4TokenSecureFormatter) DecryptToken(source []byte) ([]byte, error) { 135 | if len(source) < 32 { 136 | return nil, errors.New("Invalid token") 137 | } 138 | dest := make([]byte, len(source)) 139 | cipher, err := rc4.NewCipher(sc.key) 140 | if err != nil { 141 | return nil, err 142 | } 143 | cipher.XORKeyStream(dest, source) 144 | hasher := sha256.New() 145 | hasher.Write(dest[32:]) 146 | hash := hasher.Sum(nil) 147 | for i, b := range hash { 148 | if b != dest[i] { 149 | return nil, errors.New("Invalid token") 150 | } 151 | } 152 | return dest[32:], nil 153 | } 154 | -------------------------------------------------------------------------------- /security_test.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var _sutRC4, _sutSHA256 *TokenProvider 8 | 9 | func init() { 10 | _sutRC4 = NewTokenProvider(NewRC4TokenSecurityProvider([]byte("testkey"))) 11 | _sutSHA256 = NewTokenProvider(NewSHA256RC4TokenSecurityProvider([]byte("testkey"))) 12 | } 13 | 14 | func TestCrypt(t *testing.T) { 15 | var token string = `{"CreationDate":"2016-12-14","Expiration":"1000"}` 16 | result, err := _sutRC4.crypt([]byte(token)) 17 | if err != nil { 18 | t.Fatalf("Error %s", err.Error()) 19 | } 20 | t.Logf("Base64 Token : %v", result) 21 | } 22 | 23 | func TestDecrypt(t *testing.T) { 24 | var token string = `{"CreationDate":"2016-12-14","Expiration":"1000"}` 25 | var bToken []byte = []byte(token) 26 | t.Logf("Base64 Token : %v", bToken) 27 | result, err := _sutRC4.crypt([]byte(token)) 28 | if err != nil { 29 | t.Fatalf("Error %s", err.Error()) 30 | } 31 | t.Logf("Base64 Token : %v", result) 32 | decrypt, err := _sutRC4.decrypt(result) 33 | if err != nil { 34 | t.Fatalf("Error %s", err.Error()) 35 | } 36 | t.Logf("Base64 Token Decrypted: %v", decrypt) 37 | t.Logf("Base64 Token Decrypted: %s", decrypt) 38 | for i := range bToken { 39 | if bToken[i] != decrypt[i] { 40 | t.Fatalf("Error in decryption") 41 | } 42 | } 43 | } 44 | 45 | func TestCryptSHA256(t *testing.T) { 46 | var token string = `{"CreationDate":"2016-12-14","Expiration":"1000"}` 47 | result, err := _sutSHA256.crypt([]byte(token)) 48 | if err != nil { 49 | t.Fatalf("Error %s", err.Error()) 50 | } 51 | t.Logf("Base64 Token : %v", result) 52 | } 53 | 54 | func TestDecryptSHA256(t *testing.T) { 55 | var token string = `{"CreationDate":"2016-12-14","Expiration":"1000"}` 56 | var bToken []byte = []byte(token) 57 | t.Logf("Base64 Token : %v", bToken) 58 | result, err := _sutSHA256.crypt([]byte(token)) 59 | if err != nil { 60 | t.Fatalf("Error %s", err.Error()) 61 | } 62 | t.Logf("Base64 Token : %v", result) 63 | decrypt, err := _sutSHA256.decrypt(result) 64 | if err != nil { 65 | t.Fatalf("Error %s", err.Error()) 66 | } 67 | t.Logf("Base64 Token Decrypted: %v", decrypt) 68 | t.Logf("Base64 Token Decrypted: %s", decrypt) 69 | for i := range bToken { 70 | if bToken[i] != decrypt[i] { 71 | t.Fatalf("Error in decryption") 72 | } 73 | } 74 | } 75 | 76 | func TestDecryptSHA256_LongKey(t *testing.T) { 77 | sutSHA256 := NewTokenProvider(NewSHA256RC4TokenSecurityProvider([]byte("518baffa-b290-4c01-a150-1980f5b06a01"))) 78 | var token string = `{"CreationDate":"2016-12-14","Expiration":"1000"}` 79 | var bToken []byte = []byte(token) 80 | t.Logf("Base64 Token : %v", bToken) 81 | result, err := sutSHA256.crypt([]byte(token)) 82 | if err != nil { 83 | t.Fatalf("Error %s", err.Error()) 84 | } 85 | t.Logf("Base64 Token : %v", result) 86 | decrypt, err := sutSHA256.decrypt(result) 87 | if err != nil { 88 | t.Fatalf("Error %s", err.Error()) 89 | } 90 | t.Logf("Base64 Token Decrypted: %v", decrypt) 91 | t.Logf("Base64 Token Decrypted: %s", decrypt) 92 | for i := range bToken { 93 | if bToken[i] != decrypt[i] { 94 | t.Fatalf("Error in decryption") 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | uuid "github.com/gofrs/uuid" 9 | ) 10 | 11 | const ( 12 | TOKEN_TYPE = "Bearer" 13 | ) 14 | 15 | type Any interface{} 16 | 17 | // CredentialsVerifier defines the interface of the user and client credentials verifier. 18 | type CredentialsVerifier interface { 19 | // Validate username and password returning an error if the user credentials are wrong 20 | ValidateUser(username, password, scope string, req *http.Request) error 21 | // Validate clientId and secret returning an error if the client credentials are wrong 22 | ValidateClient(clientID, clientSecret, scope string, req *http.Request) error 23 | // Provide additional claims to the token 24 | AddClaims(credential, tokenID, tokenType, scope string) (map[string]string, error) 25 | // Optionally store the tokenID generated for the user 26 | StoreTokenId(credential, tokenID, refreshTokenID, tokenType string) error 27 | // Provide additional information to the authorization server response 28 | AddProperties(credential, tokenID, tokenType string, scope string) (map[string]string, error) 29 | // Optionally validate previously stored tokenID during refresh request 30 | ValidateTokenId(credential, tokenID, refreshTokenID, tokenType string) error 31 | } 32 | 33 | // AuthorizationCodeVerifier defines the interface of the Authorization Code verifier 34 | type AuthorizationCodeVerifier interface { 35 | // ValidateCode checks the authorization code and returns the user credential 36 | ValidateCode(clientID, clientSecret, code, redirectURI string, req *http.Request) (string, error) 37 | } 38 | 39 | // OAuthBearerServer is the OAuth 2 Bearer Server implementation. 40 | type OAuthBearerServer struct { 41 | secretKey string 42 | TokenTTL time.Duration 43 | verifier CredentialsVerifier 44 | provider *TokenProvider 45 | } 46 | 47 | // NewOAuthBearerServer creates new OAuth 2 Bearer Server 48 | func NewOAuthBearerServer(secretKey string, 49 | ttl time.Duration, 50 | verifier CredentialsVerifier, 51 | formatter TokenSecureFormatter) *OAuthBearerServer { 52 | if formatter == nil { 53 | formatter = NewSHA256RC4TokenSecurityProvider([]byte(secretKey)) 54 | } 55 | obs := &OAuthBearerServer{ 56 | secretKey: secretKey, 57 | TokenTTL: ttl, 58 | verifier: verifier, 59 | provider: NewTokenProvider(formatter)} 60 | return obs 61 | } 62 | 63 | // UserCredentials manages password grant type requests 64 | func (s *OAuthBearerServer) UserCredentials(ctx *gin.Context) { 65 | grantType := ctx.PostForm("grant_type") 66 | // grant_type password variables 67 | username := ctx.PostForm("username") 68 | password := ctx.PostForm("password") 69 | scope := ctx.PostForm("scope") 70 | if username == "" || password == "" { 71 | // get username and password from basic authorization header 72 | var err error 73 | username, password, err = GetBasicAuthentication(ctx) 74 | if err != nil { 75 | ctx.JSON(http.StatusUnauthorized, "Not authorized") 76 | return 77 | } 78 | } 79 | // grant_type refresh_token 80 | refreshToken := ctx.PostForm("refresh_token") 81 | code, resp := s.generateTokenResponse(grantType, username, password, refreshToken, scope, "", "", ctx.Request) 82 | ctx.JSON(code, resp) 83 | } 84 | 85 | // ClientCredentials manages client credentials grant type requests 86 | func (s *OAuthBearerServer) ClientCredentials(ctx *gin.Context) { 87 | grantType := ctx.PostForm("grant_type") 88 | // grant_type client_credentials variables 89 | clientId := ctx.PostForm("client_id") 90 | clientSecret := ctx.PostForm("client_secret") 91 | if clientId == "" || clientSecret == "" { 92 | // get clientId and secret from basic authorization header 93 | var err error 94 | clientId, clientSecret, err = GetBasicAuthentication(ctx) 95 | if err != nil { 96 | ctx.JSON(http.StatusUnauthorized, "Not authorized") 97 | return 98 | } 99 | } 100 | scope := ctx.PostForm("scope") 101 | // grant_type refresh_token 102 | refreshToken := ctx.PostForm("refresh_token") 103 | code, resp := s.generateTokenResponse(grantType, clientId, clientSecret, refreshToken, scope, "", "", ctx.Request) 104 | ctx.JSON(code, resp) 105 | } 106 | 107 | // AuthorizationCode manages authorization code grant type requests for the phase two of the authorization process 108 | func (s *OAuthBearerServer) AuthorizationCode(ctx *gin.Context) { 109 | grantType := ctx.PostForm("grant_type") 110 | // grant_type client_credentials variables 111 | clientId := ctx.PostForm("client_id") 112 | clientSecret := ctx.PostForm("client_secret") // not mandatory 113 | code := ctx.PostForm("code") 114 | redirectURI := ctx.PostForm("redirect_uri") // not mandatory 115 | scope := ctx.PostForm("scope") // not mandatory 116 | if clientId == "" { 117 | // get clientId and secret from basic authorization header 118 | var err error 119 | clientId, clientSecret, err = GetBasicAuthentication(ctx) 120 | if err != nil { 121 | ctx.JSON(http.StatusUnauthorized, "Not authorized") 122 | return 123 | } 124 | } 125 | status, resp := s.generateTokenResponse(grantType, clientId, clientSecret, "", scope, code, redirectURI, ctx.Request) 126 | ctx.JSON(status, resp) 127 | } 128 | 129 | // Generate token response 130 | func (s *OAuthBearerServer) generateTokenResponse(grantType, credential, secret, refreshToken, scope, code, redirectURI string, req *http.Request) (int, Any) { 131 | // check grant_Type 132 | if grantType == "password" { 133 | err := s.verifier.ValidateUser(credential, secret, scope, req) 134 | if err == nil { 135 | token, refresh, err := s.generateTokens(credential, "U", scope) 136 | if err == nil { 137 | // Store token id 138 | err = s.verifier.StoreTokenId(credential, token.Id, refresh.RefreshTokenId, token.TokenType) 139 | if err != nil { 140 | return http.StatusInternalServerError, "Storing Token Id failed" 141 | } 142 | resp, err := s.cryptTokens(token, refresh) 143 | if err == nil { 144 | return http.StatusOK, resp 145 | } else { 146 | return http.StatusInternalServerError, "Token generation failed, check security provider" 147 | } 148 | } else { 149 | return http.StatusInternalServerError, "Token generation failed, check claims" 150 | } 151 | 152 | } else { 153 | //not autorized 154 | return http.StatusUnauthorized, "Not authorized" 155 | } 156 | } else if grantType == "client_credentials" { 157 | err := s.verifier.ValidateClient(credential, secret, scope, req) 158 | if err == nil { 159 | token, refresh, err := s.generateTokens(credential, "C", scope) 160 | if err == nil { 161 | // Store token id 162 | err = s.verifier.StoreTokenId(credential, token.Id, refresh.RefreshTokenId, token.TokenType) 163 | if err != nil { 164 | return http.StatusInternalServerError, "Storing Token Id failed" 165 | } 166 | resp, err := s.cryptTokens(token, refresh) 167 | if err == nil { 168 | return http.StatusOK, resp 169 | } else { 170 | return http.StatusInternalServerError, "Token generation failed, check security provider" 171 | } 172 | } else { 173 | return http.StatusInternalServerError, "Token generation failed, check claims" 174 | } 175 | } else { 176 | //not autorized 177 | return http.StatusUnauthorized, "Not authorized" 178 | } 179 | } else if grantType == "authorization_code" { 180 | if codeVerifier, ok := s.verifier.(AuthorizationCodeVerifier); ok { 181 | user, err := codeVerifier.ValidateCode(credential, secret, code, redirectURI, req) 182 | if err == nil { 183 | token, refresh, err := s.generateTokens(user, "A", scope) 184 | if err == nil { 185 | // Store token id 186 | err = s.verifier.StoreTokenId(user, token.Id, refresh.RefreshTokenId, token.TokenType) 187 | if err != nil { 188 | return http.StatusInternalServerError, "Storing Token Id failed" 189 | } 190 | resp, err := s.cryptTokens(token, refresh) 191 | if err == nil { 192 | return http.StatusOK, resp 193 | } else { 194 | return http.StatusInternalServerError, "Token generation failed, check security provider" 195 | } 196 | } else { 197 | return http.StatusInternalServerError, "Token generation failed, check claims" 198 | } 199 | } else { 200 | //not autorized 201 | return http.StatusUnauthorized, "Not authorized" 202 | } 203 | } else { 204 | //not autorized 205 | return http.StatusUnauthorized, "Not authorized, grant type not supported" 206 | } 207 | } else if grantType == "refresh_token" { 208 | // refresh token 209 | refresh, err := s.provider.DecryptRefreshTokens(refreshToken) 210 | if err == nil { 211 | err = s.verifier.ValidateTokenId(refresh.Credential, refresh.TokenId, refresh.RefreshTokenId, refresh.TokenType) 212 | if err == nil { 213 | // generate new token 214 | token, refresh, err := s.generateTokens(refresh.Credential, refresh.TokenType, refresh.Scope) 215 | if err == nil { 216 | // Store token id 217 | err = s.verifier.StoreTokenId(refresh.Credential, token.Id, refresh.RefreshTokenId, token.TokenType) 218 | if err != nil { 219 | return http.StatusInternalServerError, "Storing Token Id failed" 220 | } 221 | resp, err := s.cryptTokens(token, refresh) 222 | if err == nil { 223 | return http.StatusOK, resp 224 | } else { 225 | return http.StatusInternalServerError, "Token generation failed" 226 | } 227 | } else { 228 | return http.StatusInternalServerError, "Token generation failed" 229 | } 230 | } else { 231 | //not autorized invalid token Id 232 | return http.StatusUnauthorized, "Not authorized invalid token" 233 | } 234 | } else { 235 | //not autorized 236 | return http.StatusUnauthorized, "Not authorized" 237 | } 238 | } else { 239 | // Invalid request 240 | return http.StatusBadRequest, "Invalid grant_type" 241 | } 242 | } 243 | 244 | func (s *OAuthBearerServer) generateTokens(username, tokenType, scope string) (token *Token, refresh *RefreshToken, err error) { 245 | token = &Token{Credential: username, ExperesIn: s.TokenTTL, CreationDate: time.Now().UTC(), TokenType: tokenType, Scope: scope} 246 | // generate token Id 247 | token.Id = uuid.Must(uuid.NewV4()).String() 248 | if s.verifier != nil { 249 | claims, err := s.verifier.AddClaims(username, token.Id, token.TokenType, token.Scope) 250 | if err == nil { 251 | token.Claims = claims 252 | } else { 253 | // claims error 254 | return nil, nil, err 255 | } 256 | } 257 | // create refresh token 258 | refresh = &RefreshToken{RefreshTokenId: uuid.Must(uuid.NewV4()).String(), TokenId: token.Id, CreationDate: time.Now().UTC(), Credential: username, TokenType: tokenType, Scope: scope} 259 | 260 | return token, refresh, nil 261 | } 262 | 263 | func (s *OAuthBearerServer) cryptTokens(token *Token, refresh *RefreshToken) (resp *TokenResponse, err error) { 264 | ctoken, err := s.provider.CryptToken(token) 265 | if err != nil { 266 | return nil, err 267 | } 268 | crefresh, err := s.provider.CryptRefreshToken(refresh) 269 | if err != nil { 270 | return nil, err 271 | } 272 | resp = &TokenResponse{Token: ctoken, RefreshToken: crefresh, TokenType: TOKEN_TYPE, ExperesIn: (int64)(s.TokenTTL / time.Second)} 273 | 274 | if s.verifier != nil { 275 | // add properties 276 | props, err := s.verifier.AddProperties(token.Credential, token.Id, token.TokenType, token.Scope) 277 | if err == nil { 278 | resp.Properties = props 279 | } 280 | } 281 | return resp, nil 282 | } 283 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | var _sut *OAuthBearerServer 11 | 12 | func init() { 13 | _sut = NewOAuthBearerServer( 14 | "mySecretKey-10101", 15 | time.Second*60, 16 | &TestUserVerifier{}, 17 | nil) 18 | } 19 | 20 | func TestGenerateTokensByUsername(t *testing.T) { 21 | token, refresh, err := _sut.generateTokens("user111", "U", "") 22 | if err == nil { 23 | t.Logf("Token: %v", token) 24 | t.Logf("Refresh Token: %v", refresh) 25 | } else { 26 | t.Fatalf("Error %s", err.Error()) 27 | } 28 | } 29 | 30 | func TestCryptTokens(t *testing.T) { 31 | token, refresh, err := _sut.generateTokens("user222", "U", "") 32 | if err == nil { 33 | t.Logf("Token: %v", token) 34 | t.Logf("Refresh Token: %v", refresh) 35 | } else { 36 | t.Fatalf("Error %s", err.Error()) 37 | } 38 | resp, err := _sut.cryptTokens(token, refresh) 39 | if err == nil { 40 | t.Logf("Response: %v", resp) 41 | } else { 42 | t.Fatalf("Error %s", err.Error()) 43 | } 44 | } 45 | 46 | func TestDecryptRefreshTokens(t *testing.T) { 47 | token, refresh, err := _sut.generateTokens("user333", "U", "") 48 | if err == nil { 49 | t.Logf("Token: %v", token) 50 | t.Logf("Refresh Token: %v", refresh) 51 | } else { 52 | t.Fatalf("Error %s", err.Error()) 53 | } 54 | resp, err := _sut.cryptTokens(token, refresh) 55 | if err == nil { 56 | t.Logf("Response: %v", resp) 57 | t.Logf("Response Refresh Token: %v", resp.RefreshToken) 58 | } else { 59 | t.Fatalf("Error %s", err.Error()) 60 | } 61 | refresh2, err := _sut.provider.DecryptRefreshTokens(resp.RefreshToken) 62 | if err == nil { 63 | t.Logf("Refresh Token Decrypted: %v", refresh2) 64 | } else { 65 | t.Fatalf("Error %s", err.Error()) 66 | } 67 | } 68 | 69 | func TestGenerateToken4Password(t *testing.T) { 70 | code, resp := _sut.generateTokenResponse("password", "user111", "password111", "", "", "", "", nil) 71 | if code != 200 { 72 | t.Fatalf("Error StatusCode = %d", code) 73 | } 74 | t.Logf("Token response: %v", resp) 75 | } 76 | 77 | func TestShouldFailGenerateToken4Password(t *testing.T) { 78 | code, _ := _sut.generateTokenResponse("password", "user111", "password4444", "", "", "", "", nil) 79 | t.Logf("Server response: %v", code) 80 | if code != 401 { 81 | t.Fatalf("Error StatusCode = %d", code) 82 | } 83 | } 84 | 85 | func TestGenerateToken4ClientCredentials(t *testing.T) { 86 | code, resp := _sut.generateTokenResponse("client_credentials", "abcdef", "12345", "", "", "", "", nil) 87 | if code != 200 { 88 | t.Fatalf("Error StatusCode = %d", code) 89 | } 90 | t.Logf("Token response: %v", resp) 91 | } 92 | 93 | func TestRefreshToken4ClientCredentials(t *testing.T) { 94 | code, resp := _sut.generateTokenResponse("client_credentials", "abcdef", "12345", "", "", "", "", nil) 95 | if code != 200 { 96 | t.Fatalf("Error StatusCode = %d", code) 97 | } 98 | t.Logf("Token Response: %v", resp) 99 | code2, resp2 := _sut.generateTokenResponse("refresh_token", "", "", resp.(*TokenResponse).RefreshToken, "", "", "", nil) 100 | if code2 != 200 { 101 | t.Fatalf("Error StatusCode = %d", code2) 102 | } 103 | t.Logf("New Token Response: %v", resp2) 104 | } 105 | 106 | // TestUserVerifier provides user credentials verifier for testing. 107 | type TestUserVerifier struct { 108 | } 109 | 110 | // Validate username and password returning an error if the user credentials are wrong 111 | func (*TestUserVerifier) ValidateUser(username, password, scope string, req *http.Request) error { 112 | if username == "user111" && password == "password111" { 113 | return nil 114 | } else if username == "user222" && password == "password222" { 115 | return nil 116 | } else if username == "user333" && password == "password333" { 117 | return nil 118 | } else { 119 | return errors.New("Wrong user") 120 | } 121 | } 122 | 123 | // Validate clientId and secret returning an error if the client credentials are wrong 124 | func (*TestUserVerifier) ValidateClient(clientId, clientSecret, scope string, req *http.Request) error { 125 | if clientId == "abcdef" && clientSecret == "12345" { 126 | return nil 127 | } else { 128 | return errors.New("Wrong client") 129 | } 130 | } 131 | 132 | // Provide additional claims to the token 133 | func (*TestUserVerifier) AddClaims(credential, tokenID, tokenType, scope string) (map[string]string, error) { 134 | claims := make(map[string]string) 135 | claims["customerId"] = "1001" 136 | claims["customerData"] = `{"OrderDate":"2016-12-14","OrderId":"9999"}` 137 | return claims, nil 138 | } 139 | 140 | // Optionally store the token Id generated for the user 141 | func (*TestUserVerifier) StoreTokenId(credential, tokenID, refreshTokenID, tokenType string) error { 142 | return nil 143 | } 144 | 145 | // Provide additional information to the token response 146 | func (*TestUserVerifier) AddProperties(credential, tokenID, tokenType, scope string) (map[string]string, error) { 147 | props := make(map[string]string) 148 | props["customerName"] = "Gopher" 149 | return props, nil 150 | } 151 | 152 | // Validate token Id 153 | func (*TestUserVerifier) ValidateTokenId(credential, tokenID, refreshTokenID, tokenType string) error { 154 | return nil 155 | } 156 | -------------------------------------------------------------------------------- /test/authserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/maxzerbini/oauth" 10 | cors "gopkg.in/gin-contrib/cors.v1" 11 | ) 12 | 13 | /* 14 | Authorization Server Example 15 | 16 | Generate Token using username & password 17 | 18 | POST http://localhost:3000/token 19 | User-Agent: Fiddler 20 | Host: localhost:3000 21 | Content-Length: 50 22 | Content-Type: application/x-www-form-urlencoded 23 | 24 | grant_type=password&username=user01&password=12345 25 | 26 | Generate Token using clientId & secret 27 | 28 | POST http://localhost:3000/auth 29 | User-Agent: Fiddler 30 | Host: localhost:3000 31 | Content-Length: 66 32 | Content-Type: application/x-www-form-urlencoded 33 | 34 | grant_type=client_credentials&client_id=abcdef&client_secret=12345 35 | 36 | Refresh Token 37 | 38 | POST http://localhost:3000/token 39 | User-Agent: Fiddler 40 | Host: localhost:3000 41 | Content-Length: 50 42 | Content-Type: application/x-www-form-urlencoded 43 | 44 | grant_type=refresh_token&refresh_token={the refresh_token obtained in the previous response} 45 | 46 | */ 47 | func main() { 48 | router := gin.New() 49 | router.Use(gin.Recovery()) 50 | router.Use(gin.Logger()) 51 | router.Use(cors.Default()) // enable Cross-Origin Resource Sharing 52 | gin.SetMode(gin.DebugMode) 53 | registerAPI(router) 54 | router.Run(":3000") 55 | } 56 | 57 | func registerAPI(router *gin.Engine) { 58 | s := oauth.NewOAuthBearerServer( 59 | "mySecretKey-10101", 60 | time.Second*120, 61 | &TestUserVerifier{}, 62 | nil) 63 | router.POST("/token", s.UserCredentials) 64 | router.POST("/auth", s.ClientCredentials) 65 | } 66 | 67 | // TestUserVerifier provides user credentials verifier for testing. 68 | type TestUserVerifier struct { 69 | } 70 | 71 | // ValidateUser validates username and password returning an error if the user credentials are wrong 72 | func (*TestUserVerifier) ValidateUser(username, password, scope string, req *http.Request) error { 73 | if username == "user01" && password == "12345" { 74 | return nil 75 | } 76 | return errors.New("Wrong user") 77 | } 78 | 79 | // ValidateClient validates clientId and secret returning an error if the client credentials are wrong 80 | func (*TestUserVerifier) ValidateClient(clientID, clientSecret, scope string, req *http.Request) error { 81 | if clientID == "abcdef" && clientSecret == "12345" { 82 | return nil 83 | } 84 | return errors.New("Wrong client") 85 | } 86 | 87 | // AddClaims provides additional claims to the token 88 | func (*TestUserVerifier) AddClaims(credential, tokenID, tokenType, scope string) (map[string]string, error) { 89 | claims := make(map[string]string) 90 | claims["customerId"] = "1001" 91 | claims["customerData"] = `{"OrderDate":"2016-12-14","OrderId":"9999"}` 92 | return claims, nil 93 | } 94 | 95 | // StoreTokenId saves the token Id generated for the user 96 | func (*TestUserVerifier) StoreTokenId(credential, tokenId, refreshTokenID, tokenType string) error { 97 | return nil 98 | } 99 | 100 | // AddProperties provides additional information to the token response 101 | func (*TestUserVerifier) AddProperties(credential, tokenId, tokenType string, scope string) (map[string]string, error) { 102 | props := make(map[string]string) 103 | props["customerName"] = "Gopher" 104 | return props, nil 105 | } 106 | 107 | // ValidateTokenId validates token Id 108 | func (*TestUserVerifier) ValidateTokenId(credential, tokenId, refreshTokenID, tokenType string) error { 109 | return nil 110 | } 111 | 112 | // ValidateCode validates token Id 113 | func (*TestUserVerifier) ValidateCode(clientID, clientSecret, code, redirectURI string, req *http.Request) (string, error) { 114 | return "", nil 115 | } 116 | -------------------------------------------------------------------------------- /test/resourceserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/maxzerbini/oauth" 6 | cors "gopkg.in/gin-contrib/cors.v1" 7 | ) 8 | 9 | /* 10 | Resource Server Example 11 | 12 | Get Customers 13 | 14 | GET http://localhost:3200/customers 15 | User-Agent: Fiddler 16 | Host: localhost:3200 17 | Content-Length: 0 18 | Content-Type: application/json 19 | Authorization: Bearer {access_token} 20 | 21 | Get Orders 22 | 23 | GET http://localhost:3200/customers/12345/orders 24 | User-Agent: Fiddler 25 | Host: localhost:3200 26 | Content-Length: 0 27 | Content-Type: application/json 28 | Authorization: Bearer {access_token} 29 | 30 | {access_token} is produced by the Authorization Server response (see example /test/authserver). 31 | 32 | */ 33 | func main() { 34 | router := gin.New() 35 | router.Use(gin.Recovery()) 36 | router.Use(gin.Logger()) 37 | router.Use(cors.Default()) // enable Cross-Origin Resource Sharing 38 | gin.SetMode(gin.DebugMode) 39 | registerAPI(router) 40 | router.Run(":3200") 41 | } 42 | 43 | func registerAPI(router *gin.Engine) { 44 | 45 | authorized := router.Group("/") 46 | // use the Bearer Athentication middleware 47 | authorized.Use(oauth.Authorize("mySecretKey-10101", nil)) 48 | 49 | authorized.GET("/customers", GetCustomers) 50 | authorized.GET("/customers/:id/orders", GetOrders) 51 | } 52 | 53 | func GetCustomers(c *gin.Context) { 54 | 55 | c.JSON(200, gin.H{ 56 | "Status": "verified", 57 | "Customer": "test001", 58 | "CustomerName": "Max", 59 | "CustomerEmail": "test@test.com", 60 | }) 61 | } 62 | 63 | func GetOrders(c *gin.Context) { 64 | 65 | c.JSON(200, gin.H{ 66 | "status": "sent", 67 | "customer": c.Param("id"), 68 | "OrderId": "100234", 69 | "TotalOrderItems": "199", 70 | }) 71 | } 72 | --------------------------------------------------------------------------------