Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of the Authorization Code with PKCE flow for public clients #65

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/go-oauth2-server.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ WORKDIR /go/src/github.com/RichardKnop/go-oauth2-server
ADD . /go/src/github.com/RichardKnop/go-oauth2-server

# Set GO111MODULE=on variable to activate module support
ENV GO111MODULE on
ENV GO111MODULE auto

# Chown the application directory to app user
RUN chown -R app:app /go/src/github.com/RichardKnop/go-oauth2-server/
Expand Down
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This service implements [OAuth 2.0 specification](https://tools.ietf.org/html/rf
* [Client Authentication](#client-authentication)
* [Grant Types](#grant-types)
* [Authorization Code](#authorization-code)
* [Authorization Code with PKCE](#authorization-code-with-pkce)
* [Implicit](#implicit)
* [Resource Owner Password Credentials](#resource-owner-password-credentials)
* [Client Credentials](#client-credentials)
Expand Down Expand Up @@ -133,6 +134,90 @@ The authorization server authenticates the client, validates the authorization c
}
```

#### Authorization Code with PKCE

https://tools.ietf.org/html/rfc7636

Authorization Code with PKCE (Proof Key for Code Exchange) allows for a safer flow than Authorization Code for public clients, e.g SPA (Single Page Apps) and Native (e.g. mobile, or compiled) applications, or any other client where the client secret cannot be reliably or safely stored.


```
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B) Client Identifier,
+----|-----+ Code Challenge, +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ Authorization Code, | |
| |>---(D)-- Code Verifier, ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
```

Key differences from plain Authorization Code flow are that a code challenge is also sent in (A), and the code verifier is send in (D) instead of basic auth and/or client_id + client_secret.

There are two challenge methods, `PLAIN` and `S256`. The User-Agent must generate both a challenge and verification code at the start. For `plain`, they are simply the same string. `PLAIN` is not recommended for use, and is reserved for situations where the `S256` challenge is unable to be supported for some technical reason [https://tools.ietf.org/html/rfc7636#section-4.2].

The code verifier must be a (high-entropy cryptographic) random string, 43 to 128 characters long, comprised of `[[A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"` [https://tools.ietf.org/html/rfc7636#section-4.1].

For `S256` the code challenge is the URL-Safe Base64 encoded SHA256 hash of the code verifier [https://tools.ietf.org/html/rfc7636#section-4.2].

##### Example

The client MUST have `public=true` in the database.

For testing, PKCE Verifier and Challenge can be created here - https://tonyxu-io.github.io/pkce-generator/.

- Verifier - `ThisIsAVerifier`
- Challenge - `f866CDl1fJqqejcBLMjQ9UdbRhT9KY0bmD9O88RNvEY`

```
http://localhost:8080/web/authorize?client_id=test_client_1&redirect_uri=https%3A%2F%2Fwww.example.com&response_type=code&state=somestate&scope=read_write&code_challenge=f866CDl1fJqqejcBLMjQ9UdbRhT9KY0bmD9O88RNvEY&code_challenge_method=S256
```

Note the `code_challenge` and `code_challenge_method` query parameters in the above URL. The redirect page will receive the same parameters as the normal Authorization Code flow.

```
https://www.example.com/?code=327adafb-393a-43c0-a0e1-d7da651ac7aa&state=somestate
```

To obtain the token, the request is similar to Authorization Code, but omiting the client secret, and adding in the code_verifier.

```
curl --compressed -v localhost:8080/v1/oauth/tokens \
-d "client_id=test_client_1" \
-d "grant_type=authorization_code" \
-d "code=327adafb-393a-43c0-a0e1-d7da651ac7aa" \
-d "redirect_uri=https://www.example.com" \
-d "code_verifier=ThisIsAVerifier"
```
Returns:
```
{
"user_id":"1",
"access_token":"60f25bd5-4bb8-408a-b39a-fe227d2b243a",
"expires_in":3600,
"token_type":"Bearer",
"scope":"read_write",
"refresh_token":"47325775-8a9b-4a35-bfd9-1685929feda6"
}
```

#### Implicit

http://tools.ietf.org/html/rfc6749#section-4.2
Expand Down
59 changes: 59 additions & 0 deletions config/envvar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package config

import (
"os"
"strconv"
)

type evBackend struct{}

func (b *evBackend) InitConfigBackend() {
}

// LoadConfig gets the JSON from ETCD and unmarshals it to the config object
func (b *evBackend) LoadConfig() (*Config, error) {

newCnf := new(Config)
newCnf.Database.Type = "postgres"
newCnf.Database.Host = os.Getenv("PGHOST")
newCnf.Database.User = os.Getenv("POSTGRES_USER")
newCnf.Database.Password = os.Getenv("POSTGRES_PASSWORD")
newCnf.Database.DatabaseName = os.Getenv("POSTGRES_DB")

port, portExists := os.LookupEnv("POSTGRES_PORT")
if portExists {
if n, err := strconv.Atoi(port); err == nil {
newCnf.Database.Port = n
} else {
newCnf.Database.Port = 5432
}
} else {
newCnf.Database.Port = 5432
}

newCnf.Database.MaxOpenConns = 5
newCnf.Database.MaxIdleConns = 5

newCnf.Oauth.AccessTokenLifetime = 3600
newCnf.Oauth.RefreshTokenLifetime = 1209600
newCnf.Oauth.AuthCodeLifetime = 3600

secret, secretExists := os.LookupEnv("SESSION_SECRET")
if secretExists {
newCnf.Session.Secret = secret
} else {
newCnf.Session.Secret = "test_secret"
}
newCnf.Session.Path = "/"
newCnf.Session.MaxAge = 604800
newCnf.Session.HTTPOnly = true

newCnf.IsDevelopment = true

return newCnf, nil
}

// RefreshConfig sets config through the pointer so config actually gets refreshed
func (b *evBackend) RefreshConfig(newCnf *Config) {
*Cnf = *newCnf
}
2 changes: 2 additions & 0 deletions config/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func NewConfig(mustLoadOnce bool, keepReloading bool, backendType string) *Confi
backend = new(etcdBackend)
case "consul":
backend = new(consulBackend)
case "ev":
backend = new(evBackend)
default:
log.FATAL.Printf("%s is not a valid backend", backendType)
os.Exit(1)
Expand Down
24 changes: 18 additions & 6 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
#!/bin/sh
#!/bin/bash

set -e
set -ex

executable="go-oauth2-server"
cmd="$@"

if [ "$1" = 'runserver' ] || [ "$1" = 'loaddata' ]; then
until $executable migrate; do
if [ "$1" = 'runserver' ] || [ "$1" = 'loaddata' ] || [ "$3" = 'runserver' ] || [ "$3" = 'loaddata' ]; then
extra_args=""
if [ "$3" = 'runserver' ] || [ "$3" = 'loaddata' ]; then
extra_args="$1 $2"
fi

until $executable $extra_args migrate; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done

$executable loaddata oauth/fixtures/scopes.yml
$executable loaddata oauth/fixtures/roles.yml
$executable $extra_args loaddata oauth/fixtures/scopes.yml
$executable $extra_args loaddata oauth/fixtures/roles.yml

if [[ -z "${FIXTURES}" ]]; then
echo "No extra fixtures"
else
echo $FIXTURES | base64 -d > /tmp/fixtures.yml
$executable $extra_args loaddata /tmp/fixtures.yml
fi
fi

>&2 echo "Postgres is up - executing command: $cmd"
Expand Down
73 changes: 15 additions & 58 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
module github.com/RichardKnop/go-oauth2-server

go 1.14

require (
cloud.google.com/go v0.36.0 // indirect
dmitri.shuralyov.com/app/changes v0.0.0-20181114035150-5af16e21babb // indirect
dmitri.shuralyov.com/service/change v0.0.0-20190203163610-217368fe4577 // indirect
git.apache.org/thrift.git v0.12.0 // indirect
github.com/RichardKnop/go-fixtures v0.0.0-20181101035649-15577dcaa372
github.com/RichardKnop/jsonhal v0.0.0-20181101035658-9ef775cfa6bf
github.com/RichardKnop/logging v0.0.0-20181101035820-b1d5d44c82d6
github.com/RichardKnop/uuid v0.0.0-20160216163710-c55201b03606
github.com/Shopify/sarama v1.20.1 // indirect
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/boltdb/bolt v1.3.1 // indirect
github.com/cockroachdb/cmux v0.0.0-20170110192607-30d10be49292 // indirect
github.com/codegangsta/negroni v1.0.0 // indirect
github.com/coreos/bbolt v1.3.2 // indirect
github.com/coreos/etcd v3.3.12+incompatible
Expand All @@ -23,20 +17,14 @@ require (
github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
github.com/gliderlabs/ssh v0.1.2 // indirect
github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/go-sql-driver/mysql v1.4.1 // indirect
github.com/gogo/protobuf v1.2.1 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/pprof v0.0.0-20190208070709-b421f19a5c07 // indirect
github.com/googleapis/gax-go v2.0.2+incompatible // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/context v1.1.1
github.com/gorilla/mux v1.7.0
github.com/gorilla/schema v1.2.0
github.com/gorilla/sessions v1.1.3
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.7.0 // indirect
github.com/hashicorp/consul v1.4.2
Expand All @@ -48,59 +36,28 @@ require (
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/jinzhu/now v1.0.0 // indirect
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/kisielk/errcheck v1.2.0 // indirect
github.com/lib/pq v1.0.0
github.com/mattn/go-colorable v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/mattn/go-sqlite3 v1.10.0
github.com/microcosm-cc/bluemonday v1.0.2 // indirect
github.com/miekg/dns v1.1.4 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mongodb/mongo-go-driver v0.3.0 // indirect
github.com/openzipkin/zipkin-go v0.1.5 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2
github.com/phyber/negroni-gzip v0.0.0-20180113114010-ef6356a5d029
github.com/pkg/errors v0.8.1 // indirect
github.com/posener/complete v1.2.1 // indirect
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/client_golang v0.9.2 // indirect
github.com/prometheus/common v0.2.0 // indirect
github.com/prometheus/procfs v0.0.0-20190219184716-e4d4a2206da0 // indirect
github.com/russross/blackfriday v2.0.0+incompatible // indirect
github.com/shurcooL/go v0.0.0-20190121191506-3fef8c783dec // indirect
github.com/shurcooL/gofontwoff v0.0.0-20181114050219-180f79e6909d // indirect
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect
github.com/shurcooL/highlight_go v0.0.0-20181215221002-9d8641ddf2e1 // indirect
github.com/shurcooL/home v0.0.0-20190204141146-5c8ae21d4240 // indirect
github.com/shurcooL/htmlg v0.0.0-20190120222857-1e8a37b806f3 // indirect
github.com/shurcooL/httpfs v0.0.0-20181222201310-74dc9339e414 // indirect
github.com/shurcooL/issues v0.0.0-20190120000219-08d8dadf8acb // indirect
github.com/shurcooL/issuesapp v0.0.0-20181229001453-b8198a402c58 // indirect
github.com/shurcooL/notifications v0.0.0-20181111060504-bcc2b3082a7a // indirect
github.com/shurcooL/octicon v0.0.0-20181222203144-9ff1a4cf27f4 // indirect
github.com/shurcooL/reactions v0.0.0-20181222204718-145cd5e7f3d1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/shurcooL/webdavfs v0.0.0-20181215192745-5988b2d638f6 // indirect
github.com/sirupsen/logrus v1.3.0 // indirect
github.com/stretchr/testify v1.3.0
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/stretchr/testify v1.7.0
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 // indirect
github.com/unrolled/secure v1.0.0
github.com/urfave/cli v0.0.0-20180106191048-75104e932ac2
github.com/urfave/negroni v1.0.0
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.opencensus.io v0.19.0 // indirect
go4.org v0.0.0-20190218023631-ce4c26f7be8e // indirect
golang.org/x/build v0.0.0-20190221024721-9e83d8587038 // indirect
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2
golang.org/x/exp v0.0.0-20190212162250-21964bba6549 // indirect
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9 // indirect
golang.org/x/perf v0.0.0-20190124201629-844a5f5b46f4 // indirect
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 // indirect
golang.org/x/tools v0.0.0-20190221000707-a754db16a40a // indirect
google.golang.org/genproto v0.0.0-20190219182410-082222b4a5c5 // indirect
google.golang.org/grpc v1.18.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9
gopkg.in/tylerb/graceful.v1 v1.2.15
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c // indirect
sourcegraph.com/sqs/pbtypes v1.0.0 // indirect
)
Loading