- Notifications
You must be signed in to change notification settings - Fork370
Extensible security first OAuth 2.0 and OpenID Connect SDK for Go.
License
ory/fosite
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
The security first OAuth2 & OpenID Connect framework forGo. Built simple, powerful and extensible. This libraryimplements peer-reviewedIETF RFC6749,counterfeits weaknesses covered in peer-reviewedIETF RFC6819 and countermeasures variousdatabase attack scenarios, keeping your application safe when that hackerpenetrates or leaks your database. OpenID Connect is implemented according toOpenID Connect Core 1.0 incorporating errata set 1and includes all flows: code, implicit, hybrid.
This library considered and implemented:
- The OAuth 2.0 Authorization Framework
- OAuth 2.0 Multiple Response Type Encoding Practices
- OAuth 2.0 Threat Model and Security Considerations
- Proof Key for Code Exchange by OAuth Public Clients
- OAuth 2.0 for Native Apps
- OpenID Connect Core 1.0
- OAuth 2.0 Pushed Authorization Request
OAuth2 and OpenID Connect are difficult protocols. If you want quick wins, westrongly encourage you to look atHydra.Hydra is a secure, high performance, cloud native OAuth2 and OpenID Connectservice that integrates with every authentication method imaginable and is builton top of Fosite.
Table of Contents
- Motivation
- API Stability
- Example
- A word on quality
- A word on security
- A word on extensibility
- Installation
- Documentation
- Contribute
- Hall of Fame
Fosite was written because our OAuth2 and OpenID Connect serviceHydra required a secure and extensibleOAuth2 library. We had to realize that nothing matching our requirements was outthere, so we decided to build it ourselves.
The core public API is almost stable as most changes will only touch the innerworkings.
We strongly encourage vendoring fosite usingdep or comparable tools.
The example does not have nice visuals but it should give you an idea of whatyou can do with Fosite and a few lines of code.
You can run this minimalistic example by doing
go get github.com/ory/fosite-examplecd $GOPATH/src/github.com/ory/fosite-exampledep ensurego install github.com/ory/fosite-examplefosite-example
There should be a server listening onlocalhost:3846.You can check out the example's source codehere.
We tried to set up as many tests as possible and test for as many cases coveredin the RFCs as possible. But we are only human. Please, feel free to add testsfor the various cases defined in the OAuth2 RFCs 6749 and 6819 or any othercases that improve the tests.
Everyone writing an RFC conform test that breaks with the currentimplementation, will receive a place in theHall of Fame!
Please be aware that Fosite only secures parts of your server side security. Youstill need to secure your apps and clients, keep your tokens safe, prevent CSRFattacks, ensure database security, use valid and strong TLS certificates andmuch more. If you need any help or advice feel free to contact our securitystaff throughour website!
We have given the various specifications, especiallyOAuth 2.0 Threat Model and Security Considerations,a very close look and included everything we thought was in the scope of thisframework. Here is a complete list of things we implemented in Fosite:
- No Cleartext Storage of Credentials
- Encryption of Credentials
- Use Short Expiration Time
- Limit Number of Usages or One-Time Usage
- Bind Token to Client id
- Automatic Revocation of Derived Tokens If Abuse Is Detected
- Binding of Refresh Token to "client_id"
- Refresh Token Rotation
- Revocation of Refresh Tokens
- Validate Pre-Registered "redirect_uri"
- Binding of Authorization "code" to "client_id"
- Binding of Authorization "code" to "redirect_uri"
- Opaque access tokens
- Opaque refresh tokens
- Ensure Confidentiality of Requests
- Use of Asymmetric CryptographyFosite ensures that redirect URIs use httpsexcept localhost but you needto implement TLS for the token and auth endpoints yourself.
Additionally, we added these safeguards:
- Enforcing random states: Without a random-looking state or OpenID Connectnonce the request will fail.
- Advanced Token Validation: Tokens are layouted as
<key>.<signature>
where<signature>
is created using HMAC-SHA256 using a global secret. Thisis what a token can look like:/tgBeUhWlAT8tM8Bhmnx+Amf8rOYOUhrDi3pGzmjP7c=.BiV/Yhma+5moTP46anxMT6cWW8gz5R5vpC9RbpwSDdM=
Sections belowSection 5 thatare not covered in the list above should be reviewed by you. If you think that aspecific section should be something that is covered in Fosite, feel free tocreate anissue. Please be aware thatOpenID Connect requires specific knowledge of the identity provider, which iswhy Fosite only implements core requirements and most things must be implementedby you (for example prompt, max_age, ui_locales, id_token_hint, userauthentication, session management, ...).
It is strongly encouraged to use the handlers shipped with Fosite as theyfollow the specs and are well tested.
Fosite is extensible ... because OAuth2 is an extensible and flexibleframework. Fosite let's you register custom token and authorize endpointhandlers with the security that the requests have been validated against theOAuth2 specs beforehand. You can easily extend Fosite's capabilities. Forexample, 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 issecure. ;)
Go 1.11+ must be installed on your system and it isrequired that you have set up your GOPATH environment variable.
go get -u github.com/ory/fosite/...
We recommend to usedep to mitigatecompatibility breaks that come with new api versions.
There is an API documentation available atgodoc.org/ory/fosite.
Fosite has three strategies for matching scopes. You can replace the defaultscope strategy if you need a custom one by implementingfosite.ScopeStrategy
.
Using the composer, setting a strategy is easy:
import"github.com/ory/fosite"varconfig=&fosite.Config{ScopeStrategy:fosite.HierarchicScopeStrategy,}
Note: To issue refresh tokens with any of the grants, you need to includetheoffline
scope in the OAuth2 request. This can be modified by theRefreshTokenScopes
compose configuration. When set to an empty array,allgrants will issue refresh tokens.
This is the default strategy, and the safest one. It is best explained bylooking at some examples:
users.*
matchesusers.read
users.*
matchesusers.read.foo
users.read
matchesusers.read
users
does not matchusers.read
users.read.*
does not matchusers.read
users.*.*
does not matchusers.read
users.*.*
matchesusers.read.own
users.*.*
matchesusers.read.own.other
users.read.*
matchesusers.read.own
users.read.*
matchesusers.read.own.other
users.write.*
does not matchusers.read.own
users.*.bar
matchesusers.baz.bar
users.*.bar
does notusers.baz.baz.bar
To requestusers.*
, a client must have exactlyusers.*
as granted scope.
This strategy is searching only for exact matches. It returns true iff the scopeis granted.
This strategy is deprecated, use it with care. Again, it is best explained bylooking at some examples:
users
matchesusers
users
matchesusers.read
users
matchesusers.read.own
users.read
matchesusers.read
users.read
matchesusers.read.own
users.read
does not matchusers.write
users.read
does not matchusers.write.own
Fosite does not natively carry translations for error messages and hints, butoffers an interface that allows the consumer to define catalog bundles and animplementation to translate. This is available through theMessageCatalog interface. The functions defined areself-explanatory. TheDefaultMessageCatalog
illustrates this. Compose confighas been extended to take in an instance of theMessageCatalog
.
There are three possible "message key" types:
- Value of
RFC6749Error.ErrorField
: This is a string likeinvalid_request
and correlates to most errors produced by Fosite. - Hint identifier passed into
RFC6749Error.WithHintIDOrDefaultf
: This func isnot used extensively in Fosite but, in time, mostWithHint
andWithHintf
will be replaced with this function. - Free text string format passed into
RFC6749Error.WithHint
andRFC6749Error.WithHintf
: This function is used in Fosite and Hydraextensively and any message catalog implementation can use the format stringparameter as the message key.
An example of a message catalog can be seen in thei18n_test.go.
This is a WIP at the moment, but effectively any scripting language can be usedto generate this. It would need to traverse all files in the source code andextract the possible message identifiers based on the different message keytypes.
Instantiating fosite by hand can be painful. Therefore we created a fewconvenience helpers available through thecompose package. It isstrongly encouraged to use these well tested composers.
In this very basic example, we will instantiate fosite with all OpenID Connectand OAuth2 handlers enabled. Please refer to theexample app for more details.
This little code snippet sets up a full-blown OAuth2 and OpenID Connect example.
package mainimport"github.com/ory/fosite"import"github.com/ory/fosite/compose"import"github.com/ory/fosite/storage"// This is the example storage that contains:// * an OAuth2 Client with id "my-client" and secrets "foobar" and "foobaz" capable of all oauth2 and open id connect grant and response types.// * a User for the resource owner password credentials grant type with username "peter" and password "secret".//// You will most likely replace this with your own logic once you set up a real world application.varstorage=storage.NewExampleStore()// This secret is being used to sign access and refresh tokens as well as// authorization codes. It must be exactly 32 bytes long.varsecret= []byte("my super secret signing password")privateKey,err:=rsa.GenerateKey(rand.Reader,2048)iferr!=nil {panic("unable to create private key")}// check the api docs of fosite.Config for further configuration optionsvarconfig=&fosite.Config{AccessTokenLifespan:time.Minute*30,GlobalSecret:secret,// ...}varoauth2Provider=compose.ComposeAllEnabled(config,storage,privateKey)// The authorize endpoint is usually at "https://mydomain.com/oauth2/auth".funcauthorizeHandlerFunc(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:=req.Context()// Let's create an AuthorizeRequest object!// It will analyze the request and extract important information like scopes, response type and others.ar,err:=oauth2Provider.NewAuthorizeRequest(ctx,req)iferr!=nil {oauth2Provider.WriteAuthorizeError(ctx,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 passwordifreq.Form.Get("username")!="peter" {rw.Header().Set("Content-Type","text/html;charset=UTF-8")rw.Write([]byte(`<h1>Login page</h1>`))rw.Write([]byte(`<p>Howdy! This is the log in page. For this example, it is enough to supply the username.</p><form method="post"><input type="text" name="username" /> <small>try peter</small><br><input type="submit"></form>`))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.// 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.mySessionData:=&fosite.DefaultSession{Username: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:=oauth2Provider.NewAuthorizeResponse(ctx,ar,mySessionData)iferr!=nil {oauth2Provider.WriteAuthorizeError(ctx,rw,ar,err)return}// Awesome, now we redirect back to the client redirect uri and pass along an authorize codeoauth2Provider.WriteAuthorizeResponse(ctx,rw,ar,response)}// The token endpoint is usually at "https://mydomain.com/oauth2/token"functokenHandlerFunc(rw http.ResponseWriter,req*http.Request) {ctx:=req.Context()// Create an empty session object that will be passed to storage implementation to populate (unmarshal) the session into.// By passing an empty session object as a "prototype" to the store, the store can use the underlying type to unmarshal the value into it.// For an example of storage implementation that takes advantage of that, see SQL Store (fosite_store_sql.go) from ory/Hydra project.mySessionData:=new(fosite.DefaultSession)// This will create an access request object and iterate through the registered TokenEndpointHandlers to validate the request.accessRequest,err:=oauth2Provider.NewAccessRequest(ctx,req,mySessionData)iferr!=nil {oauth2Provider.WriteAccessError(ctx,rw,accessRequest,err)return}ifmySessionData.Username=="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:=oauth2Provider.NewAccessResponse(ctx,accessRequest)iferr!=nil {oauth2Provider.WriteAccessError(ctx,rw,accessRequest,err)return}// All done, send the response.oauth2Provider.WriteAccessResponse(ctx,rw,accessRequest,response)// The client has a valid access token now}funcsomeResourceProviderHandlerFunc(rw http.ResponseWriter,req*http.Request) {ctx:=req.Context()requiredScope:="blogposts.create"_,ar,err:=oauth2Provider.IntrospectToken(ctx,fosite.AccessTokenFromRequest(req),fosite.AccessToken,new(fosite.DefaultSession),requiredScope)iferr!=nil {// ...}// If no error occurred the token + scope is valid and you have access to:// ar.GetClient().GetID(), ar.GetGrantedScopes(), ar.GetScopes(), ar.GetSession().UserID, ar.GetRequestedAt(), ...}
Fosite provides integration tests as well as a http server example:
- Fosite ships with an example app that runs in your browser:Example app.
- If you want to check out how to enable specific handlers, check out theintegration tests.
If you have working examples yourself, please share them with us!
Fosite does not ship a storage implementation. This is intended, becauserequirements vary with every environment. You can find a referenceimplementation atstorage/memory.go. This storage fulfillsrequirements from all OAuth2 and OpenID Connect handlers.
OAuth2 is a framework. Fosite mimics this behaviour by enabling you to replaceexisting or create new OAuth2 handlers. Of course, fosite ships handlers for allOAuth2 and OpenID Connect flows.
- Fosite OAuth2 Core Handlers implement theClient Credentials Grant,Resource Owner Password Credentials Grant,Implicit Grant,Authorization Code Grant,Refresh Token Grant
- Fosite OpenID Connect Handlers implement theAuthentication using the Authorization Code Flow,Authentication using the Implicit Flow,Authentication using the Hybrid Flow
This section is missing documentation and we welcome any contributions in thatdirection.
Please note that when using the OAuth2StatelessJWTIntrospectionFactory accesstoken revocation is not possible.
You need git and golang installed on your system.
go get -d github.com/ory/fositecd $GOPATH/src/github.com/ory/fositegit statusgit remote add myfork <url-to-your-fork>go test ./...
Simple, right? Now you are ready to go! Make sure to rungo test ./...
often,detecting problems with your code rather sooner than later. Please read[CONTRIBUTE.md] before creating pull requests and issues.
Run./generate-mocks.sh
in fosite's root directory or run the contents of[generate-mocks.sh] in a shell.
This place is reserved for the fearless bug hunters, reviewers and contributors(alphabetical order).
- agtorre:contributions,participations.
- danielchatfield:contributions,participations.
- leetal:contributions,participations.
- jrossiter:contributions,participations.
- jrossiter:contributions,participations.
- danilobuerger:contributions,participations.
Find out more about theauthor of Fosite and Hydra, andtheOry Company.
About
Extensible security first OAuth 2.0 and OpenID Connect SDK for Go.