Skip to content

Commit

Permalink
Added authorization code grant flow
Browse files Browse the repository at this point in the history
  • Loading branch information
pdt256 committed Apr 23, 2020
1 parent 8693557 commit 7c2beda
Show file tree
Hide file tree
Showing 19 changed files with 489 additions and 158 deletions.
73 changes: 68 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ docker run -p 8080:8080 inklabs/goauth2

## Client Credentials Grant

http://tools.ietf.org/html/rfc6749#section-4.4
* https://tools.ietf.org/html/rfc6749#section-4.4

```
+---------+ +---------------+
Expand Down Expand Up @@ -49,7 +49,7 @@ curl localhost:8080/token \

## Resource Owner Password Credentials

http://tools.ietf.org/html/rfc6749#section-4.3
* https://tools.ietf.org/html/rfc6749#section-4.3

```
+----------+
Expand All @@ -76,7 +76,7 @@ curl localhost:8080/token \
-u client_id_hash:client_secret_hash \
-d "grant_type=password" \
-d "[email protected]" \
-d "password=p45w0rd" \
-d "password=Pass123!" \
-d "scope=read_write"
```

Expand All @@ -92,8 +92,8 @@ curl localhost:8080/token \

## Refresh Token

https://tools.ietf.org/html/rfc6749#section-1.5
http://tools.ietf.org/html/rfc6749#section-6
* https://tools.ietf.org/html/rfc6749#section-1.5
* https://tools.ietf.org/html/rfc6749#section-6

```
+--------+ +---------------+
Expand Down Expand Up @@ -134,3 +134,66 @@ curl localhost:8080/token \
"refresh_token": "b4c69a71124641739f6a83b786b332d3"
}
```

## Authorization Code

* https://tools.ietf.org/html/rfc6749#section-4.1

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

```
open http://localhost:8080/authorize?client_id=client_id_hash&redirect_uri=https%3A%2F%2Fexample.com%2Foauth2%2Fcallback&response_type=code&state=somestate&scope=read_write
```

1. Login via the web form ([email protected] | Pass123!)
1. Click button to grant access
1. The authorization server redirects back to the redirection URI including an authorization code and any
state provided by the client

```
https://example.com/oauth2/callback?code=36e2807ee1f94252ac2d9b1d3adf2ba2&state=somestate
```

```shell script
curl localhost:8080/token \
-u client_id_hash:client_secret_hash \
-d "grant_type=authorization_code" \
-d "code=36e2807ee1f94252ac2d9b1d3adf2ba2" \
-d "redirect_uri=https://example.com/oauth2/callback"
```

```json
{
"access_token": "865382b944024b2394167d519fa80cba",
"expires_at": 1574371565,
"token_type": "Bearer",
"scope": "read_write",
"refresh_token": "48403032170e46e8af72b7cca1612b43"
}
```
9 changes: 9 additions & 0 deletions authorization_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ func (a *authorizationCode) Handle(command Command) {
},
)

case IssueAuthorizationCodeToUser:
a.emit(AuthorizationCodeWasIssuedToUser{
AuthorizationCode: c.AuthorizationCode,
UserID: c.UserID,
ClientID: c.ClientID,
ExpiresAt: c.ExpiresAt,
Scope: c.Scope,
})

}
}

Expand Down
9 changes: 8 additions & 1 deletion authorization_code_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ type RequestAccessTokenViaAuthorizationCodeGrant struct {
AuthorizationCode string `json:"authorizationCode"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectUri string `json:"redirectUri"`
RedirectURI string `json:"redirectURI"`
UserID string `json:"userID"`
}
type IssueAuthorizationCodeToUser struct {
AuthorizationCode string `json:"authorizationCode"`
UserID string `json:"userID"`
ClientID string `json:"clientID"`
ExpiresAt int64 `json:"expiresAt"`
Scope string `json:"scope"`
}
6 changes: 4 additions & 2 deletions authorization_code_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package goauth2
type AuthorizationCodeWasIssuedToUser struct {
AuthorizationCode string `json:"authorizationCode"`
UserID string `json:"userID"`
ClientID string `json:"clientID"`
ExpiresAt int64 `json:"expiresAt"`
Scope string `json:"scope"`
}
type AccessTokenWasIssuedToUserViaAuthorizationCodeGrant struct {
AuthorizationCode string `json:"authorizationCode"`
Expand All @@ -27,10 +29,10 @@ type RequestAccessTokenViaAuthorizationCodeGrantWasRejectedDueToInvalidClientApp
AuthorizationCode string `json:"authorizationCode"`
ClientID string `json:"clientID"`
}
type RequestAccessTokenViaAuthorizationCodeGrantWasRejectedDueToInvalidClientApplicationRedirectUri struct {
type RequestAccessTokenViaAuthorizationCodeGrantWasRejectedDueToInvalidClientApplicationRedirectURI struct {
AuthorizationCode string `json:"authorizationCode"`
ClientID string `json:"clientID"`
RedirectUri string `json:"redirectUri"`
RedirectURI string `json:"redirectURI"`
}
type RequestAccessTokenViaAuthorizationCodeGrantWasRejectedDueToInvalidAuthorizationCode struct {
AuthorizationCode string `json:"authorizationCode"`
Expand Down
28 changes: 28 additions & 0 deletions authorization_code_process_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package goauth2

import (
"github.com/inklabs/rangedb"
)

type authorizationCodeProcessManager struct {
commandDispatcher CommandDispatcher
}

func newAuthorizationCodeProcessManager(commandDispatcher CommandDispatcher) *authorizationCodeProcessManager {
return &authorizationCodeProcessManager{
commandDispatcher: commandDispatcher,
}
}

func (r *authorizationCodeProcessManager) Accept(record *rangedb.Record) {
switch event := record.Data.(type) {
case *AuthorizationCodeWasIssuedToUserViaAuthorizationCodeGrant:
r.commandDispatcher(IssueAuthorizationCodeToUser{
AuthorizationCode: event.AuthorizationCode,
UserID: event.UserID,
ClientID: event.ClientID,
ExpiresAt: event.ExpiresAt,
Scope: event.Scope,
})
}
}
16 changes: 8 additions & 8 deletions client_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type clientApplication struct {
IsOnBoarded bool
ClientID string
ClientSecret string
RedirectUri string
RedirectURI string
pendingEvents []rangedb.Event
}

Expand All @@ -32,7 +32,7 @@ func (a *clientApplication) apply(event rangedb.Event) {
a.IsOnBoarded = true
a.ClientID = e.ClientID
a.ClientSecret = e.ClientSecret
a.RedirectUri = e.RedirectUri
a.RedirectURI = e.RedirectURI

}
}
Expand All @@ -41,27 +41,27 @@ func (a *clientApplication) Handle(command Command) {
switch c := command.(type) {

case OnBoardClientApplication:
uri, err := url.Parse(c.RedirectUri)
uri, err := url.Parse(c.RedirectURI)
if err != nil {
a.emit(OnBoardClientApplicationWasRejectedDueToInvalidRedirectUri{
a.emit(OnBoardClientApplicationWasRejectedDueToInvalidRedirectURI{
ClientID: c.ClientID,
RedirectUri: c.RedirectUri,
RedirectURI: c.RedirectURI,
})
return
}

if uri.Scheme != "https" {
a.emit(OnBoardClientApplicationWasRejectedDueToInsecureRedirectUri{
a.emit(OnBoardClientApplicationWasRejectedDueToInsecureRedirectURI{
ClientID: c.ClientID,
RedirectUri: c.RedirectUri,
RedirectURI: c.RedirectURI,
})
return
}

a.emit(ClientApplicationWasOnBoarded{
ClientID: c.ClientID,
ClientSecret: c.ClientSecret,
RedirectUri: c.RedirectUri,
RedirectURI: c.RedirectURI,
UserID: c.UserID,
})

Expand Down
18 changes: 9 additions & 9 deletions client_application_command_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ func (h *clientApplicationCommandAuthorization) Handle(command Command) bool {
return false
}

if clientApplication.RedirectUri != c.RedirectUri {
h.emit(RequestAccessTokenViaImplicitGrantWasRejectedDueToInvalidClientApplicationRedirectUri{
if clientApplication.RedirectURI != c.RedirectURI {
h.emit(RequestAccessTokenViaImplicitGrantWasRejectedDueToInvalidClientApplicationRedirectURI{
UserID: c.UserID,
ClientID: c.ClientID,
RedirectUri: c.RedirectUri,
RedirectURI: c.RedirectURI,
})
return false
}
Expand Down Expand Up @@ -87,11 +87,11 @@ func (h *clientApplicationCommandAuthorization) Handle(command Command) bool {
return false
}

if clientApplication.RedirectUri != c.RedirectUri {
h.emit(RequestAuthorizationCodeViaAuthorizationCodeGrantWasRejectedDueToInvalidClientApplicationRedirectUri{
if clientApplication.RedirectURI != c.RedirectURI {
h.emit(RequestAuthorizationCodeViaAuthorizationCodeGrantWasRejectedDueToInvalidClientApplicationRedirectURI{
UserID: c.UserID,
ClientID: c.ClientID,
RedirectUri: c.RedirectUri,
RedirectURI: c.RedirectURI,
})
return false
}
Expand All @@ -115,11 +115,11 @@ func (h *clientApplicationCommandAuthorization) Handle(command Command) bool {
return false
}

if clientApplication.RedirectUri != c.RedirectUri {
h.emit(RequestAccessTokenViaAuthorizationCodeGrantWasRejectedDueToInvalidClientApplicationRedirectUri{
if clientApplication.RedirectURI != c.RedirectURI {
h.emit(RequestAccessTokenViaAuthorizationCodeGrantWasRejectedDueToInvalidClientApplicationRedirectURI{
AuthorizationCode: c.AuthorizationCode,
ClientID: c.ClientID,
RedirectUri: c.RedirectUri,
RedirectURI: c.RedirectURI,
})
return false
}
Expand Down
2 changes: 1 addition & 1 deletion client_application_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package goauth2
type OnBoardClientApplication struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectUri string `json:"redirectUri"`
RedirectURI string `json:"redirectURI"`
UserID string `json:"userID"`
}
type RequestAccessTokenViaClientCredentialsGrant struct {
Expand Down
10 changes: 5 additions & 5 deletions client_application_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ package goauth2
type ClientApplicationWasOnBoarded struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectUri string `json:"redirectUri"`
RedirectURI string `json:"redirectURI"`
UserID string `json:"userID"`
}
type OnBoardClientApplicationWasRejectedDueToUnAuthorizeUser struct {
ClientID string `json:"clientID"`
UserID string `json:"userID"`
}
type OnBoardClientApplicationWasRejectedDueToInsecureRedirectUri struct {
type OnBoardClientApplicationWasRejectedDueToInsecureRedirectURI struct {
ClientID string `json:"clientID"`
RedirectUri string `json:"redirectUri"`
RedirectURI string `json:"redirectURI"`
}
type OnBoardClientApplicationWasRejectedDueToInvalidRedirectUri struct {
type OnBoardClientApplicationWasRejectedDueToInvalidRedirectURI struct {
ClientID string `json:"clientID"`
RedirectUri string `json:"redirectUri"`
RedirectURI string `json:"redirectURI"`
}

// RequestAccessTokenViaClientCredentialsGrant Events
Expand Down
31 changes: 22 additions & 9 deletions cmd/goauth2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,18 @@ func main() {
}

func initDB(goauth2App *goauth2.App, store rangedb.Store) {
const userID = "445a57a41b7b43e285b51e99bba10a79"
const (
userID = "445a57a41b7b43e285b51e99bba10a79"
email = "[email protected]"
password = "Pass123"
)

shortuuid.SetRand(100)

goauth2App.Dispatch(goauth2.OnBoardUser{
UserID: userID,
Username: "[email protected]",
Password: "p45w0rd",
Username: email,
Password: password,
})

_ = store.Save(goauth2.UserWasGrantedAdministratorRole{
Expand All @@ -79,30 +83,39 @@ func initDB(goauth2App *goauth2.App, store rangedb.Store) {
goauth2App.Dispatch(goauth2.OnBoardClientApplication{
ClientID: "8895e1e5f06644ebb41c26ea5740b246",
ClientSecret: "c1e847aef925467290b4302e64f3de4e",
RedirectUri: "https://example.com/oauth2/callback",
RedirectURI: "https://example.com/oauth2/callback",
UserID: userID,
})

fmt.Println("Example commands to test grant flows:")
fmt.Println("# Client Credentials")
fmt.Println(`curl localhost:8080/token \
-u 8895e1e5f06644ebb41c26ea5740b246:c1e847aef925467290b4302e64f3de4e \
-d "grant_type=client_credentials" \
-d "scope=read_write"`)
-d "scope=read_write" -s | jq`)

fmt.Println("# Resource Owner Password Credentials")
fmt.Println(`curl localhost:8080/token \
-u 8895e1e5f06644ebb41c26ea5740b246:c1e847aef925467290b4302e64f3de4e \
-d "grant_type=password" \
-d "[email protected]" \
-d "password=p45w0rd" \
-d "scope=read_write"`)
-d "password=Pass123" \
-d "scope=read_write" -s | jq`)

fmt.Println("# Refresh Token")
fmt.Println(`curl localhost:8080/token \
-u 8895e1e5f06644ebb41c26ea5740b246:c1e847aef925467290b4302e64f3de4e \
-d "grant_type=refresh_token" \
-d "refresh_token=3cc6fa5b470642b081e3ebd29aa9b43c"`)
-d "refresh_token=3cc6fa5b470642b081e3ebd29aa9b43c" -s | jq`)

fmt.Println("# Refresh Token x2")
fmt.Println(`curl localhost:8080/token \
-u 8895e1e5f06644ebb41c26ea5740b246:c1e847aef925467290b4302e64f3de4e \
-d "grant_type=refresh_token" \
-d "refresh_token=93b5e8869a954faaa6c6ba73dfea1a09"`)
-d "refresh_token=93b5e8869a954faaa6c6ba73dfea1a09" -s | jq`)

fmt.Println("# Authorization Code")
fmt.Println(`http://0.0.0.0:8080/login?client_id=8895e1e5f06644ebb41c26ea5740b246&redirect_uri=https://example.com/oauth2/callback&response_type=code&state=somestate&scope=read_write`)
fmt.Println("user: [email protected]")
fmt.Println("pass: Pass123")
}
Loading

0 comments on commit 7c2beda

Please sign in to comment.