Skip to content

Commit b0f3e66

Browse files
authored
Merge branch 'master' into pr/158
2 parents 250fcaa + e71e51f commit b0f3e66

File tree

21 files changed

+1111
-708
lines changed

21 files changed

+1111
-708
lines changed

README.md

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
> An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications.
44
5-
[![Build][Build-Status-Image]][Build-Status-Url] [![Codecov][codecov-image]][codecov-url] [![ReportCard][reportcard-image]][reportcard-url] [![GoDoc][godoc-image]][godoc-url] [![License][license-image]][license-url]
5+
[![Build][build-status-image]][build-status-url] [![Codecov][codecov-image]][codecov-url] [![ReportCard][reportcard-image]][reportcard-url] [![GoDoc][godoc-image]][godoc-url] [![License][license-image]][license-url]
66

77
## Protocol Flow
88

9-
``` text
9+
```text
1010
+--------+ +---------------+
1111
| |--(A)- Authorization Request ->| Resource |
1212
| | | Owner |
@@ -30,13 +30,13 @@
3030

3131
### Download and install
3232

33-
``` bash
33+
```bash
3434
go get -u -v github.com/go-oauth2/oauth2/v4/...
3535
```
3636

3737
### Create file `server.go`
3838

39-
``` go
39+
```go
4040
package main
4141

4242
import (
@@ -95,7 +95,7 @@ func main() {
9595

9696
### Build and run
9797

98-
``` bash
98+
```bash
9999
go build server.go
100100

101101
./server
@@ -105,24 +105,24 @@ go build server.go
105105

106106
[http://localhost:9096/token?grant_type=client_credentials&client_id=000000&client_secret=999999&scope=read](http://localhost:9096/token?grant_type=client_credentials&client_id=000000&client_secret=999999&scope=read)
107107

108-
``` json
108+
```json
109109
{
110-
"access_token": "J86XVRYSNFCFI233KXDL0Q",
111-
"expires_in": 7200,
112-
"scope": "read",
113-
"token_type": "Bearer"
110+
"access_token": "J86XVRYSNFCFI233KXDL0Q",
111+
"expires_in": 7200,
112+
"scope": "read",
113+
"token_type": "Bearer"
114114
}
115115
```
116116

117117
## Features
118118

119-
* Easy to use
120-
* Based on the [RFC 6749](https://tools.ietf.org/html/rfc6749) implementation
121-
* Token storage support TTL
122-
* Support custom expiration time of the access token
123-
* Support custom extension field
124-
* Support custom scope
125-
* Support jwt to generate access tokens
119+
- Easy to use
120+
- Based on the [RFC 6749](https://tools.ietf.org/html/rfc6749) implementation
121+
- Token storage support TTL
122+
- Support custom expiration time of the access token
123+
- Support custom extension field
124+
- Support custom scope
125+
- Support jwt to generate access tokens
126126

127127
## Example
128128

@@ -161,27 +161,28 @@ if !ok || !token.Valid {
161161

162162
## Store Implements
163163

164-
* [BuntDB](https://github.com/tidwall/buntdb)(default store)
165-
* [Redis](https://github.com/go-oauth2/redis)
166-
* [MongoDB](https://github.com/go-oauth2/mongo)
167-
* [MySQL](https://github.com/go-oauth2/mysql)
168-
* [MySQL (Provides both client and token store)](https://github.com/imrenagi/go-oauth2-mysql)
169-
* [PostgreSQL](https://github.com/vgarvardt/go-oauth2-pg)
170-
* [DynamoDB](https://github.com/contamobi/go-oauth2-dynamodb)
171-
* [XORM](https://github.com/techknowlogick/go-oauth2-xorm)
172-
* [GORM](https://github.com/techknowlogick/go-oauth2-gorm)
173-
* [Firestore](https://github.com/tslamic/go-oauth2-firestore)
164+
- [BuntDB](https://github.com/tidwall/buntdb)(default store)
165+
- [Redis](https://github.com/go-oauth2/redis)
166+
- [MongoDB](https://github.com/go-oauth2/mongo)
167+
- [MySQL](https://github.com/go-oauth2/mysql)
168+
- [MySQL (Provides both client and token store)](https://github.com/imrenagi/go-oauth2-mysql)
169+
- [PostgreSQL](https://github.com/vgarvardt/go-oauth2-pg)
170+
- [DynamoDB](https://github.com/contamobi/go-oauth2-dynamodb)
171+
- [XORM](https://github.com/techknowlogick/go-oauth2-xorm)
172+
- [XORM (MySQL, client and token store)](https://github.com/rainlay/go-oauth2-xorm)
173+
- [GORM](https://github.com/techknowlogick/go-oauth2-gorm)
174+
- [Firestore](https://github.com/tslamic/go-oauth2-firestore)
174175

175176
## Handy Utilities
176177

177-
* [OAuth2 Proxy Logger (Debug utility that proxies interfaces and logs)](https://github.com/aubelsb2/oauth2-logger-proxy)
178+
- [OAuth2 Proxy Logger (Debug utility that proxies interfaces and logs)](https://github.com/aubelsb2/oauth2-logger-proxy)
178179

179180
## MIT License
180181

181-
Copyright (c) 2016 Lyric
182+
Copyright (c) 2016 Lyric
182183

183-
[Build-Status-Url]: https://travis-ci.org/go-oauth2/oauth2
184-
[Build-Status-Image]: https://travis-ci.org/go-oauth2/oauth2.svg?branch=master
184+
[build-status-url]: https://travis-ci.org/go-oauth2/oauth2
185+
[build-status-image]: https://travis-ci.org/go-oauth2/oauth2.svg?branch=master
185186
[codecov-url]: https://codecov.io/gh/go-oauth2/oauth2
186187
[codecov-image]: https://codecov.io/gh/go-oauth2/oauth2/branch/master/graph/badge.svg
187188
[reportcard-url]: https://goreportcard.com/report/github.com/go-oauth2/oauth2/v4

const.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package oauth2
22

3+
import (
4+
"crypto/sha256"
5+
"encoding/base64"
6+
"strings"
7+
)
8+
39
// ResponseType the type of authorization request
410
type ResponseType string
511

@@ -34,3 +40,37 @@ func (gt GrantType) String() string {
3440
}
3541
return ""
3642
}
43+
44+
// CodeChallengeMethod PCKE method
45+
type CodeChallengeMethod string
46+
47+
const (
48+
// CodeChallengePlain PCKE Method
49+
CodeChallengePlain CodeChallengeMethod = "plain"
50+
// CodeChallengeS256 PCKE Method
51+
CodeChallengeS256 CodeChallengeMethod = "S256"
52+
)
53+
54+
func (ccm CodeChallengeMethod) String() string {
55+
if ccm == CodeChallengePlain ||
56+
ccm == CodeChallengeS256 {
57+
return string(ccm)
58+
}
59+
return ""
60+
}
61+
62+
// Validate code challenge
63+
func (ccm CodeChallengeMethod) Validate(cc, ver string) bool {
64+
switch ccm {
65+
case CodeChallengePlain:
66+
return cc == ver
67+
case CodeChallengeS256:
68+
s256 := sha256.Sum256([]byte(ver))
69+
// trim padding
70+
a := strings.TrimRight(base64.URLEncoding.EncodeToString(s256[:]), "=")
71+
b := strings.TrimRight(cc, "=")
72+
return a == b
73+
default:
74+
return false
75+
}
76+
}

const_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package oauth2_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/go-oauth2/oauth2/v4"
7+
)
8+
9+
func TestValidatePlain(t *testing.T) {
10+
cc := oauth2.CodeChallengePlain
11+
if !cc.Validate("plaintest", "plaintest") {
12+
t.Fatal("not valid")
13+
}
14+
}
15+
16+
func TestValidateS256(t *testing.T) {
17+
cc := oauth2.CodeChallengeS256
18+
if !cc.Validate("W6YWc_4yHwYN-cGDgGmOMHF3l7KDy7VcRjf7q2FVF-o=", "s256test") {
19+
t.Fatal("not valid")
20+
}
21+
}
22+
23+
func TestValidateS256NoPadding(t *testing.T) {
24+
cc := oauth2.CodeChallengeS256
25+
if !cc.Validate("W6YWc_4yHwYN-cGDgGmOMHF3l7KDy7VcRjf7q2FVF-o", "s256test") {
26+
t.Fatal("not valid")
27+
}
28+
}

errors/error.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ var (
1313
ErrInvalidRefreshToken = errors.New("invalid refresh token")
1414
ErrExpiredAccessToken = errors.New("expired access token")
1515
ErrExpiredRefreshToken = errors.New("expired refresh token")
16+
ErrMissingCodeVerifier = errors.New("missing code verifier")
17+
ErrMissingCodeChallenge = errors.New("missing code challenge")
18+
ErrInvalidCodeChallenge = errors.New("invalid code challenge")
1619
)

errors/response.go

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,42 +34,51 @@ func (r *Response) SetHeader(key, value string) {
3434

3535
// https://tools.ietf.org/html/rfc6749#section-5.2
3636
var (
37-
ErrInvalidRequest = errors.New("invalid_request")
38-
ErrUnauthorizedClient = errors.New("unauthorized_client")
39-
ErrAccessDenied = errors.New("access_denied")
40-
ErrUnsupportedResponseType = errors.New("unsupported_response_type")
41-
ErrInvalidScope = errors.New("invalid_scope")
42-
ErrServerError = errors.New("server_error")
43-
ErrTemporarilyUnavailable = errors.New("temporarily_unavailable")
44-
ErrInvalidClient = errors.New("invalid_client")
45-
ErrInvalidGrant = errors.New("invalid_grant")
46-
ErrUnsupportedGrantType = errors.New("unsupported_grant_type")
37+
ErrInvalidRequest = errors.New("invalid_request")
38+
ErrUnauthorizedClient = errors.New("unauthorized_client")
39+
ErrAccessDenied = errors.New("access_denied")
40+
ErrUnsupportedResponseType = errors.New("unsupported_response_type")
41+
ErrInvalidScope = errors.New("invalid_scope")
42+
ErrServerError = errors.New("server_error")
43+
ErrTemporarilyUnavailable = errors.New("temporarily_unavailable")
44+
ErrInvalidClient = errors.New("invalid_client")
45+
ErrInvalidGrant = errors.New("invalid_grant")
46+
ErrUnsupportedGrantType = errors.New("unsupported_grant_type")
47+
ErrCodeChallengeRquired = errors.New("invalid_request")
48+
ErrUnsupportedCodeChallengeMethod = errors.New("invalid_request")
49+
ErrInvalidCodeChallengeLen = errors.New("invalid_request")
4750
)
4851

4952
// Descriptions error description
5053
var Descriptions = map[error]string{
51-
ErrInvalidRequest: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
52-
ErrUnauthorizedClient: "The client is not authorized to request an authorization code using this method",
53-
ErrAccessDenied: "The resource owner or authorization server denied the request",
54-
ErrUnsupportedResponseType: "The authorization server does not support obtaining an authorization code using this method",
55-
ErrInvalidScope: "The requested scope is invalid, unknown, or malformed",
56-
ErrServerError: "The authorization server encountered an unexpected condition that prevented it from fulfilling the request",
57-
ErrTemporarilyUnavailable: "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
58-
ErrInvalidClient: "Client authentication failed",
59-
ErrInvalidGrant: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
60-
ErrUnsupportedGrantType: "The authorization grant type is not supported by the authorization server",
54+
ErrInvalidRequest: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
55+
ErrUnauthorizedClient: "The client is not authorized to request an authorization code using this method",
56+
ErrAccessDenied: "The resource owner or authorization server denied the request",
57+
ErrUnsupportedResponseType: "The authorization server does not support obtaining an authorization code using this method",
58+
ErrInvalidScope: "The requested scope is invalid, unknown, or malformed",
59+
ErrServerError: "The authorization server encountered an unexpected condition that prevented it from fulfilling the request",
60+
ErrTemporarilyUnavailable: "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
61+
ErrInvalidClient: "Client authentication failed",
62+
ErrInvalidGrant: "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
63+
ErrUnsupportedGrantType: "The authorization grant type is not supported by the authorization server",
64+
ErrCodeChallengeRquired: "PKCE is required. code_challenge is missing",
65+
ErrUnsupportedCodeChallengeMethod: "Selected code_challenge_method not supported",
66+
ErrInvalidCodeChallengeLen: "Code challenge length must be between 43 and 128 charachters long",
6167
}
6268

6369
// StatusCodes response error HTTP status code
6470
var StatusCodes = map[error]int{
65-
ErrInvalidRequest: 400,
66-
ErrUnauthorizedClient: 401,
67-
ErrAccessDenied: 403,
68-
ErrUnsupportedResponseType: 401,
69-
ErrInvalidScope: 400,
70-
ErrServerError: 500,
71-
ErrTemporarilyUnavailable: 503,
72-
ErrInvalidClient: 401,
73-
ErrInvalidGrant: 401,
74-
ErrUnsupportedGrantType: 401,
71+
ErrInvalidRequest: 400,
72+
ErrUnauthorizedClient: 401,
73+
ErrAccessDenied: 403,
74+
ErrUnsupportedResponseType: 401,
75+
ErrInvalidScope: 400,
76+
ErrServerError: 500,
77+
ErrTemporarilyUnavailable: 503,
78+
ErrInvalidClient: 401,
79+
ErrInvalidGrant: 401,
80+
ErrUnsupportedGrantType: 401,
81+
ErrCodeChallengeRquired: 400,
82+
ErrUnsupportedCodeChallengeMethod: 400,
83+
ErrInvalidCodeChallengeLen: 400,
7584
}

example/client/client.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"context"
5+
"crypto/sha256"
6+
"encoding/base64"
57
"encoding/json"
68
"fmt"
79
"io"
@@ -24,16 +26,18 @@ var (
2426
Scopes: []string{"all"},
2527
RedirectURL: "http://localhost:9094/oauth2",
2628
Endpoint: oauth2.Endpoint{
27-
AuthURL: authServerURL + "/authorize",
28-
TokenURL: authServerURL + "/token",
29+
AuthURL: authServerURL + "/oauth/authorize",
30+
TokenURL: authServerURL + "/oauth/token",
2931
},
3032
}
3133
globalToken *oauth2.Token // Non-concurrent security
3234
)
3335

3436
func main() {
3537
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
36-
u := config.AuthCodeURL("xyz")
38+
u := config.AuthCodeURL("xyz",
39+
oauth2.SetAuthURLParam("code_challenge", genCodeChallengeS256("s256example")),
40+
oauth2.SetAuthURLParam("code_challenge_method", "S256"))
3741
http.Redirect(w, r, u, http.StatusFound)
3842
})
3943

@@ -49,7 +53,7 @@ func main() {
4953
http.Error(w, "Code not found", http.StatusBadRequest)
5054
return
5155
}
52-
token, err := config.Exchange(context.Background(), code)
56+
token, err := config.Exchange(context.Background(), code, oauth2.SetAuthURLParam("code_verifier", "s256example"))
5357
if err != nil {
5458
http.Error(w, err.Error(), http.StatusInternalServerError)
5559
return
@@ -130,3 +134,8 @@ func main() {
130134
log.Println("Client is running at 9094 port.Please open http://localhost:9094")
131135
log.Fatal(http.ListenAndServe(":9094", nil))
132136
}
137+
138+
func genCodeChallengeS256(s string) string {
139+
s256 := sha256.Sum256([]byte(s))
140+
return base64.URLEncoding.EncodeToString(s256[:])
141+
}

0 commit comments

Comments
 (0)