From a92c75531cf5bb89524cd719c9bc2c98fe709c62 Mon Sep 17 00:00:00 2001
From: Aeneas
Date: Sat, 6 Aug 2016 14:10:43 +0200
Subject: [PATCH] all: composable factories, better token validation, better
scope handling and simplify structure
* readme: add gitter chat badge closes #67
* handler: flatten packages closes #70
* openid: don't autogrant openid scope - closes #68
* all: clean up scopes / arguments - closes #66
* all: composable factories - closes #64
* all: refactor token validation - closes #63
* all: remove mandatory scope - closes #62
---
HISTORY.md | 21 +
README.md | 297 +++++++---
access_request_handler.go | 8 +-
access_request_handler_test.go | 24 +-
authorize_request_handler.go | 8 +-
authorize_request_handler_test.go | 19 +-
authorize_request_test.go | 6 +-
client.go | 54 +-
client_test.go | 29 +-
compose/compose.go | 79 +++
compose/compose_oauth2.go | 119 ++++
compose/compose_openid.go | 70 +++
compose/compose_strategy.go | 41 ++
compose/config.go | 49 ++
errors.go | 2 +-
fosite-example/main.go | 511 ++++--------------
fosite-example/pkg/handler_app_callback.go | 83 +++
fosite-example/pkg/handler_home.go | 40 ++
.../pkg/handler_oauth2_client_flow.go | 22 +
.../pkg/handler_oauth2_owner_flow.go | 41 ++
fosite-example/{store => pkg}/store.go | 27 +
fosite.go | 59 +-
fosite_test.go | 29 +-
generate-mocks.sh | 30 +-
handler/core/client/storage.go | 7 -
handler/core/validator.go | 40 --
.../flow_authorize_code_auth.go} | 41 +-
.../flow_authorize_code_auth_test.go} | 7 +-
.../flow_authorize_code_storage.go} | 5 +-
.../flow_authorize_code_token.go} | 14 +-
.../flow_authorize_code_token_test.go} | 12 +-
.../flow_authorize_implicit.go} | 35 +-
.../flow_authorize_implicit_storage.go} | 2 +-
.../flow_authorize_implicit_test.go} | 6 +-
.../client.go => oauth2/flow_client.go} | 16 +-
.../oauth2/flow_client_credentials_storage.go | 5 +
.../flow_client_credentials_test.go} | 22 +-
.../refresh.go => oauth2/flow_refresh.go} | 13 +-
.../flow_refresh_storage.go} | 5 +-
.../flow_refresh_test.go} | 6 +-
.../flow_resource_owner.go} | 20 +-
.../flow_resource_owner_storage.go} | 5 +-
.../flow_resource_owner_test.go} | 12 +-
handler/{core => oauth2}/helper.go | 6 +-
handler/{core => oauth2}/helper_test.go | 3 +-
handler/{core => oauth2}/storage.go | 2 +-
handler/{core => oauth2}/strategy.go | 2 +-
.../hmacsha.go => oauth2/strategy_hmacsha.go} | 2 +-
.../strategy_hmacsha_session.go} | 2 +-
.../strategy_hmacsha_test.go} | 2 +-
.../jwt.go => oauth2/strategy_jwt.go} | 2 +-
.../strategy_jwt_session.go} | 2 +-
.../strategy_jwt_test.go} | 2 +-
handler/oauth2/validator.go | 75 +++
handler/{core => oauth2}/validator_test.go | 23 +-
handler/oidc/explicit/explicit_token.go | 41 --
handler/oidc/hybrid/hybrid.go | 99 ----
handler/{oidc => openid}/errors.go | 2 +-
.../flow_explicit_auth.go} | 15 +-
.../flow_explicit_auth_test.go} | 12 +-
handler/openid/flow_explicit_token.go | 40 ++
.../flow_explicit_token_test.go} | 16 +-
handler/openid/flow_hybrid.go | 105 ++++
.../flow_hybrid_test.go} | 45 +-
.../implicit.go => openid/flow_implicit.go} | 34 +-
.../flow_implicit_test.go} | 39 +-
handler/{oidc => openid}/helper.go | 11 +-
handler/{oidc => openid}/helper_test.go | 11 +-
handler/{oidc => openid}/storage.go | 2 +-
handler/{oidc => openid}/strategy.go | 2 +-
.../default.go => openid/strategy_jwt.go} | 4 +-
.../strategy_jwt_test.go} | 12 +-
integration/authorize_code_grant_test.go | 49 +-
integration/authorize_implicit_grant_test.go | 29 +-
integration/basic_test.go | 24 -
integration/client_credentials_grant_test.go | 64 +--
integration/helper_endpoints_test.go | 13 +-
integration/helper_setup_test.go | 42 +-
integration/oidc_explicit_test.go | 53 +-
integration/oidc_implicit_hybrid_test.go | 64 +--
integration/refresh_token_grant_test.go | 53 +-
...e_owner_password_credentials_grant_test.go | 72 +--
internal/access_request.go | 24 +-
internal/authorize_request.go | 40 +-
internal/client.go | 20 +-
internal/core_storage.go | 124 +++++
internal/core_strategy.go | 127 +++++
...ner_storage.go => oauth2_owner_storage.go} | 2 +-
...h_storage.go => oauth2_refresh_storage.go} | 2 +-
..._storage.go => openid_id_token_storage.go} | 2 +-
internal/request.go | 24 +-
internal/validator.go | 32 +-
oauth2.go | 32 +-
request.go | 23 +-
scope.go | 8 -
scope_strategy.go | 36 ++
scope_strategy_test.go | 33 ++
scope_test.go | 15 -
validator.go | 32 +-
validator_test.go | 39 +-
100 files changed, 2038 insertions(+), 1594 deletions(-)
create mode 100644 HISTORY.md
create mode 100644 compose/compose.go
create mode 100644 compose/compose_oauth2.go
create mode 100644 compose/compose_openid.go
create mode 100644 compose/compose_strategy.go
create mode 100644 compose/config.go
create mode 100644 fosite-example/pkg/handler_app_callback.go
create mode 100644 fosite-example/pkg/handler_home.go
create mode 100644 fosite-example/pkg/handler_oauth2_client_flow.go
create mode 100644 fosite-example/pkg/handler_oauth2_owner_flow.go
rename fosite-example/{store => pkg}/store.go (80%)
delete mode 100644 handler/core/client/storage.go
delete mode 100644 handler/core/validator.go
rename handler/{core/explicit/explicit_auth.go => oauth2/flow_authorize_code_auth.go} (52%)
rename handler/{core/explicit/explicit_auth_test.go => oauth2/flow_authorize_code_auth_test.go} (93%)
rename handler/{core/explicit/storage.go => oauth2/flow_authorize_code_storage.go} (75%)
rename handler/{core/explicit/explicit_token.go => oauth2/flow_authorize_code_token.go} (85%)
rename handler/{core/explicit/explicit_token_test.go => oauth2/flow_authorize_code_token_test.go} (94%)
rename handler/{core/implicit/implicit.go => oauth2/flow_authorize_implicit.go} (63%)
rename handler/{core/implicit/storage.go => oauth2/flow_authorize_implicit_storage.go} (92%)
rename handler/{core/implicit/implicit_test.go => oauth2/flow_authorize_implicit_test.go} (94%)
rename handler/{core/client/client.go => oauth2/flow_client.go} (78%)
create mode 100644 handler/oauth2/flow_client_credentials_storage.go
rename handler/{core/client/client_test.go => oauth2/flow_client_credentials_test.go} (85%)
rename handler/{core/refresh/refresh.go => oauth2/flow_refresh.go} (90%)
rename handler/{core/refresh/storage.go => oauth2/flow_refresh_storage.go} (76%)
rename handler/{core/refresh/refresh_test.go => oauth2/flow_refresh_test.go} (97%)
rename handler/{core/owner/owner.go => oauth2/flow_resource_owner.go} (78%)
rename handler/{core/owner/storage.go => oauth2/flow_resource_owner_storage.go} (68%)
rename handler/{core/owner/owner_test.go => oauth2/flow_resource_owner_test.go} (92%)
rename handler/{core => oauth2}/helper.go (83%)
rename handler/{core => oauth2}/helper_test.go (96%)
rename handler/{core => oauth2}/storage.go (98%)
rename handler/{core => oauth2}/strategy.go (98%)
rename handler/{core/strategy/hmacsha.go => oauth2/strategy_hmacsha.go} (99%)
rename handler/{core/strategy/hmacsha_session.go => oauth2/strategy_hmacsha_session.go} (98%)
rename handler/{core/strategy/hmacsha_test.go => oauth2/strategy_hmacsha_test.go} (99%)
rename handler/{core/strategy/jwt.go => oauth2/strategy_jwt.go} (99%)
rename handler/{core/strategy/jwt_session.go => oauth2/strategy_jwt_session.go} (97%)
rename handler/{core/strategy/jwt_test.go => oauth2/strategy_jwt_test.go} (99%)
create mode 100644 handler/oauth2/validator.go
rename handler/{core => oauth2}/validator_test.go (76%)
delete mode 100644 handler/oidc/explicit/explicit_token.go
delete mode 100644 handler/oidc/hybrid/hybrid.go
rename handler/{oidc => openid}/errors.go (86%)
rename handler/{oidc/explicit/explicit_auth.go => openid/flow_explicit_auth.go} (57%)
rename handler/{oidc/explicit/explicit_auth_test.go => openid/flow_explicit_auth_test.go} (87%)
create mode 100644 handler/openid/flow_explicit_token.go
rename handler/{oidc/explicit/explicit_token_test.go => openid/flow_explicit_token_test.go} (88%)
create mode 100644 handler/openid/flow_hybrid.go
rename handler/{oidc/hybrid/hybrid_test.go => openid/flow_hybrid_test.go} (74%)
rename handler/{oidc/implicit/implicit.go => openid/flow_implicit.go} (55%)
rename handler/{oidc/implicit/implicit_test.go => openid/flow_implicit_test.go} (80%)
rename handler/{oidc => openid}/helper.go (71%)
rename handler/{oidc => openid}/helper_test.go (90%)
rename handler/{oidc => openid}/storage.go (98%)
rename handler/{oidc => openid}/strategy.go (94%)
rename handler/{oidc/strategy/default.go => openid/strategy_jwt.go} (96%)
rename handler/{oidc/strategy/default_test.go => openid/strategy_jwt_test.go} (90%)
delete mode 100644 integration/basic_test.go
create mode 100644 internal/core_storage.go
create mode 100644 internal/core_strategy.go
rename internal/{core_owner_storage.go => oauth2_owner_storage.go} (96%)
rename internal/{core_refresh_storage.go => oauth2_refresh_storage.go} (96%)
rename internal/{oidc_id_token_storage.go => openid_id_token_storage.go} (96%)
delete mode 100644 scope.go
create mode 100644 scope_strategy.go
create mode 100644 scope_strategy_test.go
delete mode 100644 scope_test.go
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 000000000..7a874978e
--- /dev/null
+++ b/HISTORY.md
@@ -0,0 +1,21 @@
+This is a list of breaking changes. As long as `1.0.0` is not released, breaking changes will be addressed as minor version
+bumps (`0.1.0` -> `0.2.0`).
+
+## 0.2.0
+
+Breaking changes:
+
+* Token validation refactored: `ValidateRequestAuthorization` is now `Validate` and does not require a http request
+but instead a token and a token hint. A token can be anything, including authorization codes, refresh tokens,
+id tokens, ...
+* Remove mandatory scope: The mandatory scope (`fosite`) has been removed as it has proven impractical.
+* Allowed OAuth2 Client scopes are now being set with `scope` instead of `granted_scopes` when using the DefaultClient.
+* There is now a scope matching strategy that can be replaced.
+* OAuth2 Client scopes are now checked on every grant type.
+* Handler subpackages such as `core/client` or `oidc/explicit` have been merged and moved one level up
+* `handler/oidc` is now `handler/openid`
+* `handler/core` is now `handler/oauth2`
+
+## 0.1.0
+
+Initial release
\ No newline at end of file
diff --git a/README.md b/README.md
index e562bf1a4..b51678ef3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
# ![Fosite security first OAuth2 framework](fosite.png)
+[![Build Status](https://travis-ci.org/ory-am/fosite.svg?branch=master)](https://travis-ci.org/ory-am/fosite?branch=master)
+[![Coverage Status](https://coveralls.io/repos/ory-am/fosite/badge.svg?branch=master&service=github&foo)](https://coveralls.io/github/ory-am/fosite?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/ory-am/fosite)](https://goreportcard.com/report/ory-am/fosite)
+
+[![Join the chat at https://gitter.im/ory-am/fosite](https://badges.gitter.im/ory-am/fosite.svg)](https://gitter.im/ory-am/fosite)
+
**The security first OAuth2 & OpenID Connect framework for [Google's Go Language](https://golang.org).**
Built simple, powerful and extensible. This library implements peer-reviewed [IETF RFC6749](https://tools.ietf.org/html/rfc6749),
counterfeits weaknesses covered in peer-reviewed [IETF RFC6819](https://tools.ietf.org/html/rfc6819) and countermeasures various database
@@ -7,36 +13,36 @@ attack scenarios, keeping your application safe when that hacker penetrates or l
implemented according to [OpenID Connect Core 1.0 incorporating errata set 1](openid.net/specs/openid-connect-core-1_0.html) and
includes all flows: code, implicit, hybrid.
-
-[![Build Status](https://travis-ci.org/ory-am/fosite.svg?branch=master)](https://travis-ci.org/ory-am/fosite?branch=master)
-[![Coverage Status](https://coveralls.io/repos/ory-am/fosite/badge.svg?branch=master&service=github&foo)](https://coveralls.io/github/ory-am/fosite?branch=master)
-[![Go Report Card](https://goreportcard.com/badge/ory-am/fosite)](https://goreportcard.com/report/ory-am/fosite)
-
-During development, we reviewed the following open specifications:
-* [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
+This library considered and implemented:
* [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
* [OAuth 2.0 Multiple Response Type Encoding Practices](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html)
-* [OAuth 2.0 Threat Model and Security Considerations](https://tools.ietf.org/html/rfc6819)
+* [OAuth 2.0 Threat Model and Security Considerations](https://tools.ietf.org/html/rfc6819) (partially)
+* [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) (partially)
+
+OAuth2 and OpenID Connect are difficult protocols. If you want quick wins, we strongly encourage you to look at [Hydra](https://github.com/ory-am/hydra).
+Hydra is a secure, high performance, cloud native OAuth2 and OpenID Connect service that integrates with every authentication method
+imaginable and is built on top of Fosite.
**Table of Contents**
-- [Motivation](#motivation)
-- [API Stability](#api-stability)
-- [Example](#example)
-- [A word on quality](#a-word-on-quality)
-- [A word on security](#a-word-on-security)
-- [A word on extensibility](#a-word-on-extensibility)
-- [Usage](#usage)
+ - [Motivation](#motivation)
+ - [API Stability](#api-stability)
+ - [Example](#example)
+ - [A word on quality](#a-word-on-quality)
+ - [A word on security](#a-word-on-security)
+ - [A word on extensibility](#a-word-on-extensibility)
- [Installation](#installation)
-- [Examples](#examples)
- - [Exemplary Storage Implementation](#exemplary-storage-implementation)
- - [Extensible handlers](#extensible-handlers)
-- [Develop fosite](#develop-fosite)
- - [Refresh mock objects](#refresh-mock-objects)
-- [Known Limitations and Issues](#known-limitations-and-issues)
-- [Hall of Fame](#hall-of-fame)
+ - [Documentation](#documentation)
+ - [Scopes](#scopes)
+ - [Quickstart](#quickstart)
+ - [Code Examples](#code-examples)
+ - [Exemplary Storage Implementation](#exemplary-storage-implementation)
+ - [Extensible handlers](#extensible-handlers)
+ - [Contribute](#contribute)
+ - [Refresh mock objects](#refresh-mock-objects)
+ - [Hall of Fame](#hall-of-fame)
@@ -115,9 +121,6 @@ Additionally, we added these safeguards:
using a global secret.
This is what a token can look like:
`/tgBeUhWlAT8tM8Bhmnx+Amf8rOYOUhrDi3pGzmjP7c=.BiV/Yhma+5moTP46anxMT6cWW8gz5R5vpC9RbpwSDdM=`
-* **Enforging scopes:** By default, you always need to include the `fosite` scope or fosite will not execute the request
- properly. Obviously, you can change the scope to `basic` or `core` but be aware that you should use scopes if you use
- OAuth2.
Sections below [Section 5](https://tools.ietf.org/html/rfc6819#section-5)
that are not covered in the list above should be reviewed by you. If you think that a specific section should be something
@@ -135,11 +138,7 @@ have been validated against the OAuth2 specs beforehand.
You can easily extend Fosite's capabilities. For example, if you want to provide OpenID Connect on top of your
OAuth2 stack, that's no problem. Or custom assertions, what ever you like and as long as it is secure. ;)
-## Usage
-
-There is an API documentation available at [godoc.org/ory-am/fosite](https://godoc.org/github.com/ory-am/fosite).
-
-### Installation
+## Installation
You will need [Go](https://golang.org) installed on your machine and it is required that you have set up your
GOPATH environment variable.
@@ -148,84 +147,206 @@ GOPATH environment variable.
go get -d github.com/ory-am/fosite
```
-We recommend to use [Glide](https://github.com/Masterminds/glide) or [Godep](https://github.com/tools/godep), because
-there might be breaking changes in the future.
+We recommend to use [Glide](https://github.com/Masterminds/glide) or [Godep](https://github.com/tools/godep) to
+mitigate compatibility breaks that come with new api versions.
-## Examples
+## Documentation
-Take a look at these real-life implementations:
-* [Integration tests](integration/)
-* [Request validation](integration/helper_endpoints_test.go) (check `func tokenInfoHandler`)
-* [Fully functional example app with all OpenID Connect and OAuth2 flows enabled](fosite-example/main.go)
+There is an API documentation available at [godoc.org/ory-am/fosite](https://godoc.org/github.com/ory-am/fosite).
-### Exemplary Storage Implementation
+### Scopes
-Fosite does not ship a storage implementation yet. To get fosite running, you need to implement `github.com/ory-am/fosite.Storage`.
-Additionally, most of the token / authorize endpoint handlers require a dedicated store implementation as well. You could however use one struct
-to implement all the signatures.
+Before we skip ahead, it is important to note how scopes are being used in fosite per default.
+The general pattern can be abstracted as `...`. Every level ("subset")
+contains all sublevels, too. For example:
-You can find a working store implementation at [fosite-example/internal/store.go](fosite-example/internal/store.go).
+* `blogposts` grants scopes `blogposts.create`, `blogposts.delete`, `blockposts.read`, `blohposts.own.read`.
+* `blogposts.create` does not grant `blogposts.delete` nor `blogposts.read` nor `blogposts.own.read`.
+* `blogposts.own.read` does not grant `blogposts.create`.
+* `blogposts.own` grants `blogposts.own.read` but not `blogposts.create`.
-### Extensible handlers
+You can replace the default scope strategy if you need a custom one by implementing `fosite.ScopeStrategy`.
+
+### Quickstart
-You can replace the Token and Authorize endpoint logic by modifying `Fosite.TokenEndpointHandlers` and
-`Fosite.AuthorizeEndpointHandlers`.
+Instantiating fosite by hand can be painful. Therefore we created a few convenience helpers available through the [compose package](/compose).
+It is strongly encouraged to use these well tested composers.
-Let's take the explicit authorize handler. He is responsible for handling the
-[authorize code workflow](https://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified#web-server-apps).
+In this very basic example, we will instantiate fosite with all OpenID Connect and OAuth2 handlers enabled. Please refer
+to the [example app](fosite-example/main.go) for more details.
-If you want to enable the handler able to handle this workflow, you can do this:
+This little code snippet sets up a full-blown OAuth2 and OpenID Connect example.
```go
-var accessTokenLifespan = time.Hour
-var authorizeCodeLifespan = time.Minute * 10
-
-var hmacStrategy = &strategy.HMACSHAStrategy{
- Enigma: &enigma.Enigma{
- GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows"),
- },
- AccessTokenLifespan: accessTokenLifespan,
- AuthorizeCodeLifespan: authorizeCodeLifespan,
+import "github.com/ory-am/fosite"
+import "github.com/ory-am/fosite/compose"
+import "github.com/ory-am/fosite/fosite-example/store"
+
+// This is the exemplary storage that contains:
+// * an OAuth2 Client with id "my-client" and secret "foobar" capable of all oauth2 and open id connect grant and response types.
+// * a User for the resource owner password credentials grant type with usename "peter" and password "secret".
+//
+// You will most likely replace this with your own logic once you set up a real world application.
+var storage = store.NewExampleStore()
+
+// This secret is being used to sign access and refresh tokens as well as authorize codes.
+var secret = []byte{"my super secret password"}
+
+// check the api docs of compose.Config for further configuration options
+var config = compose.Config {
+ AccessTokenLifespan: time.Minute * 30,
+ // ...
}
-// var store = ...
+var oauth2Provider = compose.ComposeAllEnabled(config *Config, storage, secret, privateKey)
+
+// The session will be persisted by the store and made available when e.g. validating tokens or handling token endpoint requests.
+// The default OAuth2 and OpenID Connect handlers require the session to implement a few methods. Apart from that, the
+// session struct can be anything you want it to be.
+type session struct {
+ UserID string
+ Foobar int
+
+ // this is used in the OAuth2 handlers. You can set per-session expiry times here, for example.
+ *oauth2Strat.HMACSession
+
+ // this is used by the OpenID connect handlers. You can set custom id token headers and claims.
+ *oidcStrat.DefaultSession
+}
-var f = NewFosite(store)
+// The authorize endpoint is usually at "https://mydomain.com/oauth2/auth".
+func authorizeHandlerFunc(rw http.ResponseWriter, req *http.Request) {
+ // This context will be passed to all methods. It doesn't fulfill a real purpose in the standard library but could be used
+ // to abort database lookups or similar things.
+ ctx := NewContext()
+
+ // Let's create an AuthorizeRequest object!
+ // It will analyze the request and extract important information like scopes, response type and others.
+ ar, err := oauth2.NewAuthorizeRequest(ctx, req)
+ if err != nil {
+ oauth2.WriteAuthorizeError(rw, ar, err)
+ return
+ }
+
+ // Normally, this would be the place where you would check if the user is logged in and gives his consent.
+ // We're simplifying things and just checking if the request includes a valid username and password
+ if req.Form.Get("username") != "peter" {
+ rw.Header().Set("Content-Type", "text/html; charset=utf-8")
+ rw.Write([]byte(`
Login page
`))
+ rw.Write([]byte(`
+
Howdy! This is the log in page. For this example, it is enough to supply the username.
+
+ `))
+ return
+ }
+
+ // Now that the user is authorized, we set up a session. When validating / looking up tokens, we additionally get
+ // the session. You can store anything you want in it.
+ mySessionData := &session{
+ UserID: req.Form.Get("username")
+ }
+
+ // It's also wise to check the requested scopes, e.g.:
+ // if authorizeRequest.GetScopes().Has("admin") {
+ // http.Error(rw, "you're not allowed to do that", http.StatusForbidden)
+ // return
+ // }
+
+ // Now we need to get a response. This is the place where the AuthorizeEndpointHandlers kick in and start processing the request.
+ // NewAuthorizeResponse is capable of running multiple response type handlers which in turn enables this library
+ // to support open id connect.
+ response, err := oauth2.NewAuthorizeResponse(ctx, req, ar, mySessionData)
+ if err != nil {
+ oauth2.WriteAuthorizeError(rw, ar, err)
+ return
+ }
+
+ // Awesome, now we redirect back to the client redirect uri and pass along an authorize code
+ oauth2.WriteAuthorizeResponse(rw, ar, response)
+}
-// Let's enable the explicit authorize code grant!
-explicitHandler := &explicit.AuthorizeExplicitGrantTypeHandler{
- AccessTokenStrategy: hmacStrategy,
- RefreshTokenStrategy: hmacStrategy,
- AuthorizeCodeStrategy: hmacStrategy,
- Store: store,
- AuthCodeLifespan: authorizeCodeLifespan,
- AccessTokenLifespan: accessTokenLifespan,
+// The token endpoint is usually at "https://mydomain.com/oauth2/token"
+func tokenHandlerFunc(rw http.ResponseWriter, req *http.Request) {
+ ctx := NewContext()
+ mySessionData := &session{}
+
+ // This will create an access request object and iterate through the registered TokenEndpointHandlers to validate the request.
+ accessRequest, err := oauth2.NewAccessRequest(ctx, req, mySessionData)
+ if err != nil {
+ oauth2.WriteAccessError(rw, accessRequest, err)
+ return
+ }
+
+ if mySessionData.UserID == "super-admin-guy" {
+ // do something...
+ }
+
+ // Next we create a response for the access request. Again, we iterate through the TokenEndpointHandlers
+ // and aggregate the result in response.
+ response, err := oauth2.NewAccessResponse(ctx, req, accessRequest)
+ if err != nil {
+ oauth2.WriteAccessError(rw, accessRequest, err)
+ return
+ }
+
+ // All done, send the response.
+ oauth2.WriteAccessResponse(rw, accessRequest, response)
+
+ // The client has a valid access token now
}
-// Please note that order matters!
-f.AuthorizeEndpointHandlers.Append(explicitHandler)
-f.TokenEndpointHandlers.Append(explicitHandler)
+func someResourceProviderHandlerFunc(rw http.ResponseWriter, req *http.Request) {
+ ctx := NewContext()
+ mySessionData := &session{}
+ requiredScope := "blogposts.create"
+
+ ar, err := oauth2.ValidateToken(ctx, fosite.AccessTokenFromRequest(req), mySessionData, requiredScope)
+ if err != nil {
+ // ...
+ }
+
+ // if now error occurred the token + scope is valid and you have access to:
+ // ar.GetClient().GetID(), ar.GetGrantedScopes(), ar.GetScopes(), mySessionData.UserID, ar.GetRequestedAt(), ...
+}
```
-As you probably noticed, there are two types of handlers, one for the [authorization */auth* endpoint](https://tools.ietf.org/html/rfc6749#section-3.1) and one for the [token
-*/token* endpoint](https://tools.ietf.org/html/rfc6749#section-3.2). The `AuthorizeExplicitEndpointHandler` implements
-API requirements for both endpoints, while, for example, the `AuthorizeImplicitEndpointHandler` only implements
-the `AuthorizeEndpointHandler` API.
+### Code Examples
+
+Fosite provides integration tests as well as a http server example:
-You can find a complete list of handlers inside the [handler directory](handler). A short list is documented here:
+* Fosite ships with an example app that runs in your browser: [Example app](fosite-example/main.go).
+* If you want to check out how to enable specific handlers, check out the [integration tests](integration/).
-* `github.com/ory-am/fosite/handler/core/explicit.AuthorizeExplicitEndpointHandler` implements the
- [Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1)
-* `github.com/ory-am/fosite/handler/core/implicit.AuthorizeImplicitEndpointHandler` implements the
- [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2)
-* `github.com/ory-am/fosite/handler/core/token/owner.TokenROPasswordCredentialsEndpointHandler` implements the
- [Resource Owner Password Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.3)
-* `github.com/ory-am/fosite/handler/core/token/client.TokenClientCredentialsEndpointHandler` implements the
- [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4)
+If you have working examples yourself, please share them with us!
-There are also [OpenID Connect Handlers available](handler/oidc).
+### Exemplary Storage Implementation
-## Develop fosite
+Fosite does not ship a storage implementation. This is intended, because requirements vary with every environment.
+You can find a reference implementation at [fosite-example/internal/store.go](fosite-example/internal/store.go).
+This storage fulfills requirements from all OAuth2 and OpenID Conenct handlers.
+
+### Extensible handlers
+
+OAuth2 is a framework. Fosite mimics this behaviour by enabling you to replace existing or create new OAuth2 handlers.
+Of course, fosite ships handlers for all OAuth2 and OpenID Connect flows.
+
+* **[Fosite OAuth2 Core Handlers](handler/oauth2)** implement the [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4),
+ [Resource Owner Password Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.3),
+ [Implicit Grant](https://tools.ietf.org/html/rfc6749#section-4.2),
+ [Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1),
+ [Refresh Token Grant](https://tools.ietf.org/html/rfc6749#section-6)
+* **[Fosite OpenID Connect Handlers](handler/openid)** implement the
+ [Authentication using the Authorization Code Flow](http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth),
+ [Authentication using the Implicit Flow](http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth),
+ [Authentication using the Hybrid Flow](http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth)
+
+
+This section is missing documentation and we welcome any contributions in that direction.
+
+## Contribute
You need git and golang installed on your system.
@@ -238,16 +359,12 @@ go test ./...
```
Simple, right? Now you are ready to go! Make sure to run `go test ./...` often, detecting problems with your code
-rather sooner than later.
+rather sooner than later. Please read [CONTRIBUTE.md] before creating pull requests and issues.
### Refresh mock objects
Run `./generate-mocks.sh` in fosite's root directory or run the contents of [generate-mocks.sh] in a shell.
-## Known Limitations and Issues
-
-* Validator handler receives empty Request object
-
## Hall of Fame
This place is reserved for the fearless bug hunters, reviewers and contributors (alphabetical order).
diff --git a/access_request_handler.go b/access_request_handler.go
index 203841b88..00351042c 100644
--- a/access_request_handler.go
+++ b/access_request_handler.go
@@ -47,7 +47,7 @@ func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session
return accessRequest, errors.New("Session must not be nil")
}
- accessRequest.Scopes = removeEmpty(strings.Split(r.PostForm.Get("scope"), " "))
+ accessRequest.SetRequestedScopes(removeEmpty(strings.Split(r.PostForm.Get("scope"), " ")))
accessRequest.GrantTypes = removeEmpty(strings.Split(r.PostForm.Get("grant_type"), " "))
if len(accessRequest.GrantTypes) < 1 {
return accessRequest, errors.Wrap(ErrInvalidRequest, "No grant type given")
@@ -83,11 +83,5 @@ func (f *Fosite) NewAccessRequest(ctx context.Context, r *http.Request, session
if !found {
return nil, errors.Wrap(ErrInvalidRequest, "")
}
-
- if !accessRequest.GetScopes().Has(f.GetMandatoryScope()) {
- return accessRequest, errors.Wrap(ErrInvalidScope, "")
- }
-
- accessRequest.GrantScope(f.GetMandatoryScope())
return accessRequest, nil
}
diff --git a/access_request_handler_test.go b/access_request_handler_test.go
index 62df3c033..9b7b0aead 100644
--- a/access_request_handler_test.go
+++ b/access_request_handler_test.go
@@ -13,7 +13,6 @@ import (
"github.com/ory-am/fosite/internal"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
- "golang.org/x/net/context"
)
func TestNewAccessRequest(t *testing.T) {
@@ -140,28 +139,7 @@ func TestNewAccessRequest(t *testing.T) {
store.EXPECT().GetClient(gomock.Eq("foo")).Return(client, nil)
client.EXPECT().GetHashedSecret().Return([]byte("foo"))
hasher.EXPECT().Compare(gomock.Eq([]byte("foo")), gomock.Eq([]byte("bar"))).Return(nil)
- handler.EXPECT().HandleTokenEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(_ context.Context, _ *http.Request, a AccessRequester) {
- a.SetScopes([]string{"asdfasdf"})
- }).Return(nil)
- },
- handlers: TokenEndpointHandlers{handler},
- expectErr: ErrInvalidScope,
- },
- {
- header: http.Header{
- "Authorization": {basicAuth("foo", "bar")},
- },
- method: "POST",
- form: url.Values{
- "grant_type": {"foo"},
- },
- mock: func() {
- store.EXPECT().GetClient(gomock.Eq("foo")).Return(client, nil)
- client.EXPECT().GetHashedSecret().Return([]byte("foo"))
- hasher.EXPECT().Compare(gomock.Eq([]byte("foo")), gomock.Eq([]byte("bar"))).Return(nil)
- handler.EXPECT().HandleTokenEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Do(func(_ context.Context, _ *http.Request, a AccessRequester) {
- a.SetScopes([]string{DefaultMandatoryScope})
- }).Return(nil)
+ handler.EXPECT().HandleTokenEndpointRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
},
handlers: TokenEndpointHandlers{handler},
expect: &AccessRequest{
diff --git a/authorize_request_handler.go b/authorize_request_handler.go
index 3279af81f..5295f31ac 100644
--- a/authorize_request_handler.go
+++ b/authorize_request_handler.go
@@ -62,12 +62,6 @@ func (c *Fosite) NewAuthorizeRequest(ctx context.Context, r *http.Request) (Auth
request.State = state
// Remove empty items from arrays
- request.Scopes = removeEmpty(strings.Split(r.Form.Get("scope"), " "))
-
- if !request.Scopes.Has(c.GetMandatoryScope()) {
- return request, errors.Wrap(ErrInvalidScope, "mandatory scope is missing")
- }
- request.GrantScope(c.GetMandatoryScope())
-
+ request.SetRequestedScopes(removeEmpty(strings.Split(r.Form.Get("scope"), " ")))
return request, nil
}
diff --git a/authorize_request_handler_test.go b/authorize_request_handler_test.go
index 55bb3cd4c..55bf47beb 100644
--- a/authorize_request_handler_test.go
+++ b/authorize_request_handler_test.go
@@ -133,21 +133,6 @@ func TestNewAuthorizeRequest(t *testing.T) {
},
},
/* success case */
- {
- desc: "should pass",
- conf: &Fosite{Store: store},
- query: url.Values{
- "redirect_uri": {"https://foo.bar/cb"},
- "client_id": {"1234"},
- "response_type": {"code"},
- "state": {"strong-state"},
- "scope": {"foo bar"},
- },
- mock: func() {
- store.EXPECT().GetClient("1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}}, nil)
- },
- expectedError: ErrInvalidScope,
- },
{
desc: "should pass",
conf: &Fosite{Store: store},
@@ -156,7 +141,7 @@ func TestNewAuthorizeRequest(t *testing.T) {
"client_id": {"1234"},
"response_type": {"code token"},
"state": {"strong-state"},
- "scope": {DefaultMandatoryScope + " foo bar"},
+ "scope": {"foo bar"},
},
mock: func() {
store.EXPECT().GetClient("1234").Return(&DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}}, nil)
@@ -167,7 +152,7 @@ func TestNewAuthorizeRequest(t *testing.T) {
State: "strong-state",
Request: Request{
Client: &DefaultClient{RedirectURIs: []string{"https://foo.bar/cb"}},
- Scopes: []string{DefaultMandatoryScope, "foo", "bar"},
+ Scopes: []string{"foo", "bar"},
},
},
},
diff --git a/authorize_request_test.go b/authorize_request_test.go
index 5e6e51d17..9c18dcf91 100644
--- a/authorize_request_test.go
+++ b/authorize_request_test.go
@@ -91,15 +91,15 @@ func TestAuthorizeRequest(t *testing.T) {
assert.Equal(t, c.ar.RedirectURI, c.ar.GetRedirectURI(), "%d", k)
assert.Equal(t, c.ar.RequestedAt, c.ar.GetRequestedAt(), "%d", k)
assert.Equal(t, c.ar.ResponseTypes, c.ar.GetResponseTypes(), "%d", k)
- assert.Equal(t, c.ar.Scopes, c.ar.GetScopes(), "%d", k)
+ assert.Equal(t, c.ar.Scopes, c.ar.GetRequestedScopes(), "%d", k)
assert.Equal(t, c.ar.State, c.ar.GetState(), "%d", k)
assert.Equal(t, c.isRedirValid, c.ar.IsRedirectURIValid(), "%d", k)
c.ar.GrantScope("foo")
c.ar.SetSession(&struct{}{})
- c.ar.SetScopes([]string{"foo"})
+ c.ar.SetRequestedScopes([]string{"foo"})
assert.True(t, c.ar.GetGrantedScopes().Has("foo"))
- assert.True(t, c.ar.GetScopes().Has("foo"))
+ assert.True(t, c.ar.GetRequestedScopes().Has("foo"))
assert.Equal(t, &struct{}{}, c.ar.GetSession())
}
}
diff --git a/client.go b/client.go
index 46d295904..c875e3c57 100644
--- a/client.go
+++ b/client.go
@@ -1,13 +1,5 @@
package fosite
-import "strings"
-
-// Scopes is a list of scopes.
-type Scopes interface {
- // Fulfill returns true if requestScope is fulfilled by the scope list.
- Grant(requestScope string) bool
-}
-
// Client represents a client or an app.
type Client interface {
// GetID returns the client ID.
@@ -28,8 +20,8 @@ type Client interface {
// Returns the client's owner.
GetOwner() string
- // Returns the scopes this client was granted.
- GetGrantedScopes() Scopes
+ // Returns the scopes this client is allowed to request.
+ GetScopes() Arguments
}
// DefaultClient is a simple default implementation of the Client interface.
@@ -40,7 +32,7 @@ type DefaultClient struct {
RedirectURIs []string `json:"redirect_uris" gorethink:"redirect_uris"`
GrantTypes []string `json:"grant_types" gorethink:"grant_types"`
ResponseTypes []string `json:"response_types" gorethink:"response_types"`
- GrantedScopes []string `json:"granted_scopes" gorethink:"granted_scopes"`
+ Scopes []string `json:"scopes" gorethink:"scopes"`
Owner string `json:"owner" gorethink:"owner"`
PolicyURI string `json:"policy_uri" gorethink:"policy_uri"`
TermsOfServiceURI string `json:"tos_uri" gorethink:"tos_uri"`
@@ -49,40 +41,6 @@ type DefaultClient struct {
Contacts []string `json:"contacts" gorethink:"contacts"`
}
-type DefaultScopes struct {
- Scopes []string
-}
-
-func (s *DefaultScopes) Grant(requestScope string) bool {
- for _, scope := range s.Scopes {
- // foo == foo -> true
- if scope == requestScope {
- return true
- }
-
- // picture.read > picture -> false (scope picture includes read, write, ...)
- if len(scope) > len(requestScope) {
- continue
- }
-
- needles := strings.Split(requestScope, ".")
- haystack := strings.Split(scope, ".")
- haystackLen := len(haystack) - 1
- for k, needle := range needles {
- if haystackLen < k {
- return true
- }
-
- current := haystack[k]
- if current != needle {
- continue
- }
- }
- }
-
- return false
-}
-
func (c *DefaultClient) GetID() string {
return c.ID
}
@@ -95,10 +53,8 @@ func (c *DefaultClient) GetHashedSecret() []byte {
return c.Secret
}
-func (c *DefaultClient) GetGrantedScopes() Scopes {
- return &DefaultScopes{
- Scopes: c.GrantedScopes,
- }
+func (c *DefaultClient) GetScopes() Arguments {
+ return c.Scopes
}
func (c *DefaultClient) GetGrantTypes() Arguments {
diff --git a/client_test.go b/client_test.go
index 6037a9e1a..671b2924e 100644
--- a/client_test.go
+++ b/client_test.go
@@ -13,41 +13,18 @@ func TestDefaultClient(t *testing.T) {
RedirectURIs: []string{"foo", "bar"},
ResponseTypes: []string{"foo", "bar"},
GrantTypes: []string{"foo", "bar"},
+ Scopes: []string{"fooscope"},
}
+
assert.Equal(t, sc.ID, sc.GetID())
assert.Equal(t, sc.RedirectURIs, sc.GetRedirectURIs())
assert.Equal(t, sc.Secret, sc.GetHashedSecret())
assert.EqualValues(t, sc.ResponseTypes, sc.GetResponseTypes())
assert.EqualValues(t, sc.GrantTypes, sc.GetGrantTypes())
-
- assert.False(t, sc.GetGrantedScopes().Grant("foo.bar.baz"))
- assert.False(t, sc.GetGrantedScopes().Grant("foo.bar"))
- assert.False(t, sc.GetGrantedScopes().Grant("foo"))
-
- sc.GrantedScopes = []string{"foo.bar", "bar.baz", "baz.baz.1", "baz.baz.2", "baz.baz.3", "baz.baz.baz"}
- assert.True(t, sc.GetGrantedScopes().Grant("foo.bar.baz"))
- assert.True(t, sc.GetGrantedScopes().Grant("baz.baz.baz"))
- assert.True(t, sc.GetGrantedScopes().Grant("foo.bar"))
- assert.False(t, sc.GetGrantedScopes().Grant("foo"))
-
- assert.True(t, sc.GetGrantedScopes().Grant("bar.baz"))
- assert.True(t, sc.GetGrantedScopes().Grant("bar.baz.zad"))
- assert.False(t, sc.GetGrantedScopes().Grant("bar"))
-
- assert.False(t, sc.GetGrantedScopes().Grant("baz"))
-
- sc.GrantedScopes = []string{"fosite.keys.create", "fosite.keys.get", "fosite.keys.delete", "fosite.keys.update"}
- assert.True(t, sc.GetGrantedScopes().Grant("fosite.keys.delete"))
- assert.True(t, sc.GetGrantedScopes().Grant("fosite.keys.get"))
- assert.True(t, sc.GetGrantedScopes().Grant("fosite.keys.get"))
- assert.True(t, sc.GetGrantedScopes().Grant("fosite.keys.update"))
+ assert.EqualValues(t, sc.Scopes, sc.GetScopes())
sc.GrantTypes = []string{}
sc.ResponseTypes = []string{}
assert.Equal(t, "code", sc.GetResponseTypes()[0])
assert.Equal(t, "authorization_code", sc.GetGrantTypes()[0])
}
-
-func TestDefaultScope(t *testing.T) {
-
-}
diff --git a/compose/compose.go b/compose/compose.go
new file mode 100644
index 000000000..2f9ff005c
--- /dev/null
+++ b/compose/compose.go
@@ -0,0 +1,79 @@
+package compose
+
+import (
+ "crypto/rsa"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/ory-am/fosite"
+ "github.com/ory-am/fosite/hash"
+)
+
+type handler func(config *Config, storage interface{}, strategy interface{}) interface{}
+
+// Compose takes a config, a storage, a strategy and handlers to instantiate an OAuth2Provider:
+//
+// import "github.com/ory-am/fosite/compose"
+//
+// // var storage = new(MyFositeStorage)
+// var config = Config {
+// AccessTokenLifespan: time.Minute * 30,
+// // check Config for further configuration options
+// }
+//
+// var strategy = NewOAuth2HMACStrategy(config)
+//
+// var oauth2Provider = Compose(
+// config,
+// storage,
+// strategy,
+// NewOAuth2AuthorizeExplicitHandler,
+// OAuth2ClientCredentialsGrantFactory,
+// // for a complete list refer to the docs of this package
+// )
+func Compose(config *Config, storage interface{}, strategy interface{}, handlers ...handler) fosite.OAuth2Provider {
+ f := &fosite.Fosite{
+ Store: storage.(fosite.Storage),
+ AuthorizeEndpointHandlers: fosite.AuthorizeEndpointHandlers{},
+ TokenEndpointHandlers: fosite.TokenEndpointHandlers{},
+ TokenValidators: fosite.TokenValidators{},
+ Hasher: &hash.BCrypt{WorkFactor: config.GetHashCost()},
+ Logger: &logrus.Logger{},
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ }
+
+ for _, h := range handlers {
+ res := h(config, storage, strategy)
+ if ah, ok := res.(fosite.AuthorizeEndpointHandler); ok {
+ f.AuthorizeEndpointHandlers.Append(ah)
+ }
+ if th, ok := res.(fosite.TokenEndpointHandler); ok {
+ f.TokenEndpointHandlers.Append(th)
+ }
+ if tv, ok := res.(fosite.TokenValidator); ok {
+ f.TokenValidators.Append(tv)
+ }
+ }
+
+ return f
+}
+
+// ComposeAllEnabled returns a fosite instance with all OAuth2 and OpenID Connect handlers enabled.
+func ComposeAllEnabled(config *Config, storage interface{}, secret []byte, key *rsa.PrivateKey) fosite.OAuth2Provider {
+ return Compose(
+ config,
+ storage,
+ &CommonStrategy{
+ CoreStrategy: NewOAuth2HMACStrategy(config, secret),
+ OpenIDConnectTokenStrategy: NewOpenIDConnectStrategy(key),
+ },
+ OAuth2AuthorizeExplicitFactory,
+ OAuth2AuthorizeImplicitFactory,
+ OAuth2ClientCredentialsGrantFactory,
+ OAuth2RefreshTokenGrantFactory,
+ OAuth2ResourceOwnerPasswordCredentialsFactory,
+
+ OpenIDConnectExplicit,
+ OpenIDConnectImplicit,
+ OpenIDConnectHybrid,
+ )
+}
diff --git a/compose/compose_oauth2.go b/compose/compose_oauth2.go
new file mode 100644
index 000000000..09cc526b7
--- /dev/null
+++ b/compose/compose_oauth2.go
@@ -0,0 +1,119 @@
+package compose
+
+import (
+ "github.com/ory-am/fosite"
+ "github.com/ory-am/fosite/handler/oauth2"
+)
+
+// OAuth2AuthorizeExplicitFactory creates an OAuth2 authorize code grant ("authorize explicit flow") handler and registers
+// an access token, refresh token and authorize code validator.
+func OAuth2AuthorizeExplicitFactory(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *oauth2.AuthorizeExplicitGrantHandler
+ *oauth2.CoreValidator
+ }{
+ AuthorizeExplicitGrantHandler: &oauth2.AuthorizeExplicitGrantHandler{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy),
+ AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy),
+ AuthorizeCodeGrantStorage: storage.(oauth2.AuthorizeCodeGrantStorage),
+ AuthCodeLifespan: config.GetAuthorizeCodeLifespan(),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ CoreValidator: &oauth2.CoreValidator{
+ CoreStrategy: strategy.(oauth2.CoreStrategy),
+ CoreStorage: storage.(oauth2.CoreStorage),
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ }
+}
+
+// OAuth2ClientCredentialsGrantFactory creates an OAuth2 client credentials grant handler and registers
+// an access token, refresh token and authorize code validator.
+func OAuth2ClientCredentialsGrantFactory(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *oauth2.ClientCredentialsGrantHandler
+ *oauth2.CoreValidator
+ }{
+ ClientCredentialsGrantHandler: &oauth2.ClientCredentialsGrantHandler{
+ HandleHelper: &oauth2.HandleHelper{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ AccessTokenStorage: storage.(oauth2.AccessTokenStorage),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ },
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ CoreValidator: &oauth2.CoreValidator{
+ CoreStrategy: strategy.(oauth2.CoreStrategy),
+ CoreStorage: storage.(oauth2.CoreStorage),
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ }
+}
+
+// OAuth2RefreshTokenGrantFactory creates an OAuth2 refresh grant handler and registers
+// an access token, refresh token and authorize code validator.
+func OAuth2RefreshTokenGrantFactory(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *oauth2.RefreshTokenGrantHandler
+ *oauth2.CoreValidator
+ }{
+ RefreshTokenGrantHandler: &oauth2.RefreshTokenGrantHandler{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy),
+ RefreshTokenGrantStorage: storage.(oauth2.RefreshTokenGrantStorage),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ },
+ CoreValidator: &oauth2.CoreValidator{
+ CoreStrategy: strategy.(oauth2.CoreStrategy),
+ CoreStorage: storage.(oauth2.CoreStorage),
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ }
+}
+
+// OAuth2AuthorizeImplicitFactory creates an OAuth2 implicit grant ("authorize implicit flow") handler and registers
+// an access token, refresh token and authorize code validator.
+func OAuth2AuthorizeImplicitFactory(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *oauth2.AuthorizeImplicitGrantTypeHandler
+ *oauth2.CoreValidator
+ }{
+ AuthorizeImplicitGrantTypeHandler: &oauth2.AuthorizeImplicitGrantTypeHandler{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ AccessTokenStorage: storage.(oauth2.AccessTokenStorage),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ CoreValidator: &oauth2.CoreValidator{
+ CoreStrategy: strategy.(oauth2.CoreStrategy),
+ CoreStorage: storage.(oauth2.CoreStorage),
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ }
+}
+
+// OAuth2ResourceOwnerPasswordCredentialsFactory creates an OAuth2 resource owner password credentials grant handler and registers
+// an access token, refresh token and authorize code validator.
+func OAuth2ResourceOwnerPasswordCredentialsFactory(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *oauth2.ResourceOwnerPasswordCredentialsGrantHandler
+ *oauth2.CoreValidator
+ }{
+ ResourceOwnerPasswordCredentialsGrantHandler: &oauth2.ResourceOwnerPasswordCredentialsGrantHandler{
+ ResourceOwnerPasswordCredentialsGrantStorage: storage.(oauth2.ResourceOwnerPasswordCredentialsGrantStorage),
+ HandleHelper: &oauth2.HandleHelper{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ AccessTokenStorage: storage.(oauth2.AccessTokenStorage),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ },
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ CoreValidator: &oauth2.CoreValidator{
+ CoreStrategy: strategy.(oauth2.CoreStrategy),
+ CoreStorage: storage.(oauth2.CoreStorage),
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ }
+}
diff --git a/compose/compose_openid.go b/compose/compose_openid.go
new file mode 100644
index 000000000..3816d3960
--- /dev/null
+++ b/compose/compose_openid.go
@@ -0,0 +1,70 @@
+package compose
+
+import (
+ "github.com/ory-am/fosite"
+ "github.com/ory-am/fosite/handler/oauth2"
+ "github.com/ory-am/fosite/handler/openid"
+)
+
+// OpenIDConnectExplicit creates an OpenID Connect explicit ("authorize code flow") grant handler. You must add this handler
+// *after* you have added an OAuth2 authorize code handler!
+func OpenIDConnectExplicit(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *openid.OpenIDConnectExplicitHandler
+ }{
+ OpenIDConnectExplicitHandler: &openid.OpenIDConnectExplicitHandler{
+ OpenIDConnectRequestStorage: storage.(openid.OpenIDConnectRequestStorage),
+ IDTokenHandleHelper: &openid.IDTokenHandleHelper{
+ IDTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
+ },
+ },
+ }
+}
+
+// OpenIDConnectImplicit creates an OpenID Connect implicit ("implicit flow") grant handler. You must add this handler
+// *after* you have added an OAuth2 authorize implicit handler!
+func OpenIDConnectImplicit(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *openid.OpenIDConnectImplicitHandler
+ }{
+ OpenIDConnectImplicitHandler: &openid.OpenIDConnectImplicitHandler{
+ AuthorizeImplicitGrantTypeHandler: &oauth2.AuthorizeImplicitGrantTypeHandler{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ AccessTokenStorage: storage.(oauth2.AccessTokenStorage),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ },
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ IDTokenHandleHelper: &openid.IDTokenHandleHelper{
+ IDTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
+ },
+ },
+ }
+}
+
+// OpenIDConnectHybrid creates an OpenID Connect hybrid grant handler. You must add this handler
+// *after* you have added an OAuth2 authorize code and implicit authorize handler!
+func OpenIDConnectHybrid(config *Config, storage interface{}, strategy interface{}) interface{} {
+ return &struct {
+ *openid.OpenIDConnectHybridHandler
+ }{
+ OpenIDConnectHybridHandler: &openid.OpenIDConnectHybridHandler{
+ AuthorizeExplicitGrantHandler: &oauth2.AuthorizeExplicitGrantHandler{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ RefreshTokenStrategy: strategy.(oauth2.RefreshTokenStrategy),
+ AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy),
+ AuthorizeCodeGrantStorage: storage.(oauth2.AuthorizeCodeGrantStorage),
+ AuthCodeLifespan: config.GetAuthorizeCodeLifespan(),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ },
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ AuthorizeImplicitGrantTypeHandler: &oauth2.AuthorizeImplicitGrantTypeHandler{
+ AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
+ AccessTokenStorage: storage.(oauth2.AccessTokenStorage),
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ },
+ IDTokenHandleHelper: &openid.IDTokenHandleHelper{
+ IDTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
+ },
+ },
+ }
+}
diff --git a/compose/compose_strategy.go b/compose/compose_strategy.go
new file mode 100644
index 000000000..946371b99
--- /dev/null
+++ b/compose/compose_strategy.go
@@ -0,0 +1,41 @@
+package compose
+
+import (
+ "crypto/rsa"
+
+ "github.com/ory-am/fosite/handler/oauth2"
+ "github.com/ory-am/fosite/handler/openid"
+ "github.com/ory-am/fosite/token/hmac"
+ "github.com/ory-am/fosite/token/jwt"
+)
+
+type CommonStrategy struct {
+ oauth2.CoreStrategy
+ openid.OpenIDConnectTokenStrategy
+}
+
+func NewOAuth2HMACStrategy(config *Config, secret []byte) *oauth2.HMACSHAStrategy {
+ return &oauth2.HMACSHAStrategy{
+ Enigma: &hmac.HMACStrategy{
+ GlobalSecret: secret,
+ },
+ AccessTokenLifespan: config.GetAccessTokenLifespan(),
+ AuthorizeCodeLifespan: config.GetAuthorizeCodeLifespan(),
+ }
+}
+
+func NewOAuth2JWTStrategy(key *rsa.PrivateKey) *oauth2.RS256JWTStrategy {
+ return &oauth2.RS256JWTStrategy{
+ RS256JWTStrategy: &jwt.RS256JWTStrategy{
+ PrivateKey: key,
+ },
+ }
+}
+
+func NewOpenIDConnectStrategy(key *rsa.PrivateKey) *openid.DefaultStrategy {
+ return &openid.DefaultStrategy{
+ RS256JWTStrategy: &jwt.RS256JWTStrategy{
+ PrivateKey: key,
+ },
+ }
+}
diff --git a/compose/config.go b/compose/config.go
new file mode 100644
index 000000000..d015ad16c
--- /dev/null
+++ b/compose/config.go
@@ -0,0 +1,49 @@
+package compose
+
+import "time"
+
+type Config struct {
+ // AccessTokenLifespan sets how long an access token is going to be valid. Defaults to one hour.
+ AccessTokenLifespan time.Duration
+
+ // AuthorizeCodeLifespan sets how long an authorize code is going to be valid. Defaults to fifteen minutes.
+ AuthorizeCodeLifespan time.Duration
+
+ // IDTokenLifespan sets how long an id token is going to be valid. Defaults to one hour.
+ IDTokenLifespan time.Duration
+
+ // HashCost sets the cost of the password hashing cost. Defaults to 12.
+ HashCost int
+}
+
+// GetAuthorizeCodeLifespan returns how long an authorize code should be valid. Defaults to one fifteen minutes.
+func (c *Config) GetAuthorizeCodeLifespan() time.Duration {
+ if c.AuthorizeCodeLifespan == 0 {
+ return time.Minute * 15
+ }
+ return c.AuthorizeCodeLifespan
+}
+
+// GeIDTokenLifespan returns how long an id token should be valid. Defaults to one hour.
+func (c *Config) GetIDTokenLifespan() time.Duration {
+ if c.IDTokenLifespan == 0 {
+ return time.Hour
+ }
+ return c.IDTokenLifespan
+}
+
+// GetAccessTokenLifespan returns how long a refresh token should be valid. Defaults to one hour.
+func (c *Config) GetAccessTokenLifespan() time.Duration {
+ if c.AccessTokenLifespan == 0 {
+ return time.Hour
+ }
+ return c.AccessTokenLifespan
+}
+
+// GetAccessTokenLifespan returns how long a refresh token should be valid. Defaults to one hour.
+func (c *Config) GetHashCost() int {
+ if c.HashCost == 0 {
+ return 12
+ }
+ return c.HashCost
+}
diff --git a/errors.go b/errors.go
index ff7d97e37..aa9d77c97 100644
--- a/errors.go
+++ b/errors.go
@@ -21,7 +21,7 @@ var (
ErrInvalidClient = errors.New("Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)")
ErrInvalidState = errors.Errorf("The state is missing or has less than %d characters and is therefore considered too weak", MinParameterEntropy)
ErrInsufficientEntropy = errors.Errorf("The request used a security parameter (e.g., anti-replay, anti-csrf) with insufficient entropy (minimum of %d characters)", MinParameterEntropy)
- ErrMisconfiguration = errors.New("The request failed because of a misconfiguration")
+ ErrMisconfiguration = errors.New("The request failed because of an internal error that is probably caused by misconfiguration")
ErrNotFound = errors.New("Could not find the requested resource(s)")
)
diff --git a/fosite-example/main.go b/fosite-example/main.go
index 49fdd24d0..b3c3e32ba 100644
--- a/fosite-example/main.go
+++ b/fosite-example/main.go
@@ -1,73 +1,64 @@
package main
import (
+ "crypto/rand"
+ "crypto/rsa"
"fmt"
"log"
"net/http"
- "net/url"
- "reflect"
- "time"
-
"os/exec"
+ "time"
- "crypto/rand"
- "crypto/rsa"
-
- . "github.com/ory-am/fosite"
- exampleStore "github.com/ory-am/fosite/fosite-example/store"
- "github.com/ory-am/fosite/handler/core"
- coreclient "github.com/ory-am/fosite/handler/core/client"
- "github.com/ory-am/fosite/handler/core/explicit"
- "github.com/ory-am/fosite/handler/core/implicit"
- "github.com/ory-am/fosite/handler/core/owner"
- "github.com/ory-am/fosite/handler/core/refresh"
- "github.com/ory-am/fosite/handler/core/strategy"
- "github.com/ory-am/fosite/handler/oidc"
- oidcexplicit "github.com/ory-am/fosite/handler/oidc/explicit"
- "github.com/ory-am/fosite/handler/oidc/hybrid"
- oidcimplicit "github.com/ory-am/fosite/handler/oidc/implicit"
- oidcstrategy "github.com/ory-am/fosite/handler/oidc/strategy"
- "github.com/ory-am/fosite/token/hmac"
+ "github.com/ory-am/fosite"
+ "github.com/ory-am/fosite/compose"
+ helpers "github.com/ory-am/fosite/fosite-example/pkg"
+ core "github.com/ory-am/fosite/handler/oauth2"
+ "github.com/ory-am/fosite/handler/openid"
"github.com/ory-am/fosite/token/jwt"
- "github.com/parnurzeal/gorequest"
"github.com/pkg/errors"
goauth "golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)
// This is an exemplary storage instance. We will add a client and a user to it so we can use these later on.
-var store = &exampleStore.Store{
- IDSessions: make(map[string]Requester),
- Clients: map[string]*DefaultClient{
- "my-client": {
- ID: "my-client",
- Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar"
- RedirectURIs: []string{"http://localhost:3846/callback"},
- ResponseTypes: []string{"id_token", "code", "token"},
- GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
- GrantedScopes: []string{"fosite", "offline"},
- },
- },
- Users: map[string]exampleStore.UserRelation{
- "peter": {
- // This store simply checks for equality, a real storage implementation would obviously use
- // a hashing algorithm for encrypting the user password.
- Username: "peter",
- Password: "foobar",
- },
- },
- AuthorizeCodes: map[string]Requester{},
- Implicit: map[string]Requester{},
- AccessTokens: map[string]Requester{},
- RefreshTokens: map[string]Requester{},
+var store = helpers.NewExampleStore()
+
+var config = new(compose.Config)
+
+// Because we are using oauth2 and open connect id, we use this little helper to combine the two in one
+// variable.
+var strat = compose.CommonStrategy{
+ // alternatively you could use OAuth2Strategy: compose.NewOAuth2JWTStrategy(mustRSAKey())
+ CoreStrategy: compose.NewOAuth2HMACStrategy(config, []byte("some-super-cool-secret-that-nobody-knows")),
+
+ // open id connect strategy
+ OpenIDConnectTokenStrategy: compose.NewOpenIDConnectStrategy(mustRSAKey()),
}
+var oauth2 = compose.Compose(
+ config,
+ store,
+ strat,
+
+ // enabled handlers
+ compose.OAuth2AuthorizeExplicitFactory,
+ compose.OAuth2AuthorizeImplicitFactory,
+ compose.OAuth2ClientCredentialsGrantFactory,
+ compose.OAuth2RefreshTokenGrantFactory,
+ compose.OAuth2ResourceOwnerPasswordCredentialsFactory,
+
+ // be aware that open id connect factories need to be added after oauth2 factories to work properly.
+ compose.OpenIDConnectExplicit,
+ compose.OpenIDConnectImplicit,
+ compose.OpenIDConnectHybrid,
+)
+
// A valid oauth2 client (check the store) that additionally requests an OpenID Connect id token
var clientConf = goauth.Config{
ClientID: "my-client",
ClientSecret: "foobar",
RedirectURL: "http://localhost:3846/callback",
- Scopes: []string{"fosite", "openid", "offline"},
+ Scopes: []string{"photos", "openid", "offline"},
Endpoint: goauth.Endpoint{
TokenURL: "http://localhost:3846/token",
AuthURL: "http://localhost:3846/auth",
@@ -82,210 +73,47 @@ var appClientConf = clientcredentials.Config{
TokenURL: "http://localhost:3846/token",
}
-// You can decide if you want to use HMAC or JWT or another strategy for generating authorize codes and access / refresh tokens
-var hmacStrategy = &strategy.HMACSHAStrategy{
- Enigma: &hmac.HMACStrategy{
- GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows"),
- },
- AccessTokenLifespan: time.Hour,
- AuthorizeCodeLifespan: time.Hour,
-}
-
-// You can decide if you want to use HMAC or JWT or another strategy for generating authorize codes and access / refresh tokens
-// The JWT strategy is mandatory for issuing ID Tokens (OpenID Connect)
-//
-// NOTE One thing to keep in mind is that the power of JWT does not mean anything
-// when used as an authorize token, since the authorize token really just should
-// be a random string that is hard to guess.
-var jwtStrategy = &strategy.RS256JWTStrategy{
- RS256JWTStrategy: &jwt.RS256JWTStrategy{
- PrivateKey: mustRSAKey(),
- },
-}
-
-// This strategy is used for issuing OpenID Conenct id tokens
-var idtokenStrategy = &oidcstrategy.DefaultStrategy{
- RS256JWTStrategy: &jwt.RS256JWTStrategy{
- PrivateKey: mustRSAKey(),
- },
-}
-
-// Change below to change the signing method (hmacStrategy or jwtStrategy)
-var selectedStrategy = hmacStrategy
-
// A session is passed from the `/auth` to the `/token` endpoint. You probably want to store data like: "Who made the request",
// "What organization does that person belong to" and so on.
// For our use case, the session will meet the requirements imposed by JWT access tokens, HMAC access tokens and OpenID Connect
// ID Tokens plus a custom field
type session struct {
User string
- *strategy.JWTSession
- *oidcstrategy.DefaultSession
-}
-
-type stackTracer interface {
- StackTrace() errors.StackTrace
-}
-
-// newSession is a helper function for creating a new session
-func newSession(user string) *session {
- return &session{
- User: user,
- JWTSession: &strategy.JWTSession{
- JWTClaims: &jwt.JWTClaims{
- Issuer: "https://fosite.my-application.com",
- Subject: user,
- Audience: "https://my-client.my-application.com",
- ExpiresAt: time.Now().Add(time.Hour * 6),
- IssuedAt: time.Now(),
- },
- JWTHeader: &jwt.Headers{
- Extra: make(map[string]interface{}),
- },
- },
- DefaultSession: &oidcstrategy.DefaultSession{
- Claims: &jwt.IDTokenClaims{
- Issuer: "https://fosite.my-application.com",
- Subject: user,
- Audience: "https://my-client.my-application.com",
- ExpiresAt: time.Now().Add(time.Hour * 6),
- IssuedAt: time.Now(),
- },
- Headers: &jwt.Headers{
- Extra: make(map[string]interface{}),
- },
- HMACSession: &strategy.HMACSession{},
- },
- }
-}
-
-// fositeFactory creates a new Fosite instance with all features enabled
-func fositeFactory() OAuth2Provider {
- // Instantiate a new fosite instance
- f := NewFosite(store)
-
- // Set the default access token lifespan to one hour
- accessTokenLifespan := time.Hour
-
- // Most handlers are composable. This little helper is used by some of the handlers below.
- oauth2HandleHelper := &core.HandleHelper{
- AccessTokenStrategy: selectedStrategy,
- AccessTokenStorage: store,
- AccessTokenLifespan: accessTokenLifespan,
- }
-
- // This handler is responsible for the authorization code grant flow
- explicitHandler := &explicit.AuthorizeExplicitGrantTypeHandler{
- AccessTokenStrategy: selectedStrategy,
- RefreshTokenStrategy: selectedStrategy,
- AuthorizeCodeStrategy: selectedStrategy,
- AuthorizeCodeGrantStorage: store,
- AuthCodeLifespan: time.Minute * 10,
- AccessTokenLifespan: accessTokenLifespan,
- }
- // In order to "activate" the handler, we need to add it to fosite
- f.AuthorizeEndpointHandlers.Append(explicitHandler)
-
- // Because this handler both handles `/auth` and `/token` endpoint requests, we need to add him to
- // both registries.
- f.TokenEndpointHandlers.Append(explicitHandler)
-
- // This handler is responsible for the implicit flow. The implicit flow does not return an authorize code
- // but instead returns the access token directly via an url fragment.
- implicitHandler := &implicit.AuthorizeImplicitGrantTypeHandler{
- AccessTokenStrategy: selectedStrategy,
- AccessTokenStorage: store,
- AccessTokenLifespan: accessTokenLifespan,
- }
- f.AuthorizeEndpointHandlers.Append(implicitHandler)
-
- // This handler is responsible for the client credentials flow. This flow is used when you want to
- // authorize a client instead of an user.
- clientHandler := &coreclient.ClientCredentialsGrantHandler{
- HandleHelper: oauth2HandleHelper,
- }
- f.TokenEndpointHandlers.Append(clientHandler)
-
- // This handler is responsible for the resource owner password credentials grant. In general, this
- // is a flow which should not be used but could be useful in legacy environments. It uses a
- // user's credentials (username, password) to issue an access token.
- ownerHandler := &owner.ResourceOwnerPasswordCredentialsGrantHandler{
- HandleHelper: oauth2HandleHelper,
- ResourceOwnerPasswordCredentialsGrantStorage: store,
- }
- f.TokenEndpointHandlers.Append(ownerHandler)
-
- // This handler is responsible for the refresh token grant. This type is used when you want to exchange
- // a refresh token for a new refresh token and a new access token.
- refreshHandler := &refresh.RefreshTokenGrantHandler{
- AccessTokenStrategy: selectedStrategy,
- RefreshTokenStrategy: selectedStrategy,
- RefreshTokenGrantStorage: store,
- AccessTokenLifespan: accessTokenLifespan,
- }
- f.TokenEndpointHandlers.Append(refreshHandler)
-
- // This helper is similar to oauth2HandleHelper but for OpenID Connect handlers.
- oidcHelper := &oidc.IDTokenHandleHelper{IDTokenStrategy: idtokenStrategy}
-
- // The OpenID Connect Authorize Code Flow.
- oidcExplicit := &oidcexplicit.OpenIDConnectExplicitHandler{
- OpenIDConnectRequestStorage: store,
- IDTokenHandleHelper: oidcHelper,
- }
- f.AuthorizeEndpointHandlers.Append(oidcExplicit)
- // Because this handler both handles `/auth` and `/token` endpoint requests, we need to add him to
- // both registries.
- f.TokenEndpointHandlers.Append(oidcExplicit)
-
- // The OpenID Connect Implicit Flow.
- oidcImplicit := &oidcimplicit.OpenIDConnectImplicitHandler{
- IDTokenHandleHelper: oidcHelper,
- AuthorizeImplicitGrantTypeHandler: implicitHandler,
- }
- f.AuthorizeEndpointHandlers.Append(oidcImplicit)
-
- // The OpenID Connect Hybrid Flow.
- oidcHybrid := &hybrid.OpenIDConnectHybridHandler{
- IDTokenHandleHelper: oidcHelper,
- AuthorizeExplicitGrantTypeHandler: explicitHandler,
- AuthorizeImplicitGrantTypeHandler: implicitHandler,
- }
- f.AuthorizeEndpointHandlers.Append(oidcHybrid)
-
- // Add a request validator for Access Tokens to fosite
- f.AuthorizedRequestValidators.Append(&core.CoreValidator{
- AccessTokenStrategy: hmacStrategy,
- AccessTokenStorage: store,
- })
-
- return f
+ *core.HMACSession
+ *core.JWTSession
+ *openid.DefaultSession
}
-// This is our fosite instance
-var oauth2 = fositeFactory()
-
func main() {
- // Set up some endpoints. You could also use gorilla/mux or any other router.
-
+ // Set up oauth2 endpoints. You could also use gorilla/mux or any other router.
http.HandleFunc("/auth", authEndpoint)
http.HandleFunc("/token", tokenEndpoint)
- http.HandleFunc("/", homeHandler)
- http.HandleFunc("/callback", callbackHandler)
- http.HandleFunc("/client", clientEndpoint)
- http.HandleFunc("/owner", ownerEndpoint)
+ // some helper handlers for easily creating access tokens etc
+ // show some links on the index
+ http.HandleFunc("/", helpers.HomeHandler(clientConf))
+
+ // complete a client credentials flow
+ http.HandleFunc("/client", helpers.ClientEndpoint(appClientConf))
+
+ // complete a resource owner password credentials flow
+ http.HandleFunc("/owner", helpers.OwnerHandler(clientConf))
+
+ // validate tokens
http.HandleFunc("/protected-api", validateEndpoint)
- fmt.Printf("Please open your webbrowser at http://localhost:3846")
+ // the oauth2 callback endpoint
+ http.HandleFunc("/callback", helpers.CallbackHandler(clientConf))
+
+ fmt.Println("Please open your webbrowser at http://localhost:3846")
_ = exec.Command("open", "http://localhost:3846").Run()
log.Fatal(http.ListenAndServe(":3846", nil))
}
func tokenEndpoint(rw http.ResponseWriter, req *http.Request) {
// This context will be passed to all methods.
- ctx := NewContext()
+ ctx := fosite.NewContext()
// Create an empty session object which will be passed to the request handlers
mySessionData := newSession("")
@@ -320,7 +148,7 @@ func tokenEndpoint(rw http.ResponseWriter, req *http.Request) {
func authEndpoint(rw http.ResponseWriter, req *http.Request) {
// This context will be passed to all methods.
- ctx := NewContext()
+ ctx := fosite.NewContext()
// Let's create an AuthorizeRequest object!
// It will analyze the request and extract important information like scopes, response type and others.
@@ -332,24 +160,34 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
}
// You have now access to authorizeRequest, Code ResponseTypes, Scopes ...
+ var requestedScopes string
+ for _, this := range ar.GetRequestedScopes() {
+ requestedScopes += fmt.Sprintf(`
%s
`, this, this)
+ }
+
// Normally, this would be the place where you would check if the user is logged in and gives his consent.
// We're simplifying things and just checking if the request includes a valid username and password
- if req.Form.Get("username") != "peter" {
+ req.ParseForm()
+ if req.PostForm.Get("username") != "peter" {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.Write([]byte(`
Howdy! This is the log in page. For this example, it is enough to supply the username.
try peter
- `))
+ `, requestedScopes)))
return
}
- // we allow issuing of refresh tokens per default
- if ar.GetScopes().Has("offline") {
- ar.GrantScope("offline")
+ // let's see what scopes the user gave consent to
+ for _, scope := range req.PostForm["scopes"] {
+ ar.GrantScope(scope)
}
// Now that the user is authorized, we set up a session:
@@ -396,11 +234,10 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
}
func validateEndpoint(rw http.ResponseWriter, req *http.Request) {
- ctx := NewContext()
- req.Header.Add("Authorization", "bearer "+req.URL.Query().Get("token"))
+ ctx := fosite.NewContext()
mySessionData := newSession("peter")
- ar, err := oauth2.ValidateRequestAuthorization(ctx, req, mySessionData, "fosite")
+ ar, err := oauth2.ValidateToken(ctx, req.URL.Query().Get("token"), fosite.AccessToken, mySessionData)
if err != nil {
fmt.Fprintf(rw, "