Cylon Oauth2 Client and Authorization Server
- Let's visualise our component system
- Generate your bootstrap-cover system
- Add Oauth2 components
- Let's focus on Oauth2
- Oauth Client
- Oauth Provider :authorization-server
A couple of months before, juxt/cylon added Oauth2 client and provider functionality using a modular approach. This post, using an example integration project, tries to explain the implementation design details and the easy way to integrate Cylon Oauth2 client and provider in your component project.
I'd like to clear that modular-cylon-example has been generated using the modular template
bootstrap-cover following instructions that you can find on modularity.org. On top of this code I only translate the minimum needed code (mostly authored by Malcolm Sparks) to get working juxt/cylon oauth2 functionality.
$ lein new modular foo bootstrap-cover
As you can see bootstrap-cover template, provides you with a :http-listener (modular.http-kit/Webserver) and four webservices (modular.bidi/WebService), all of them are instances of modular.bidi/StaticResourceService (to provide jquery, bootstrap and public resources) except :bootstrap-cover-website-website that makes the dynamic website responses.
Note that although :modular-bidi-router-webrouter implements WebService too, is modular.ring/WebRequestHandler protocol the requirement for :http-listener-listener to use it. You can find more info about modular.bidi/Router here.
Note that this demo, trying to be simple, uses atom backed stores not intended to be used in production environments (you'l loose all your persistent data each time your app restarts). You can replace that persistence implementation by any one persistence implementations you prefer (for example postgre), only you'll have to implement the required protocol.
You can see now last bootstrap-cover components plus 22 more :) . Don't be afraid I'll work a bit on getting clear this first diagram. Bootstrap-cover components are highlighted in yellow and oauth components specifically written in this demo project in orange (related with mailing, form rendering and atom storing). Following this graph we can distinguish 2 servers, the new :authorization-server-http-listener component tree that represents the Oauth provider, and the old one :http-listener-listener that has the :webapp-oauth-client included
As you can see in the system diagram there're a lot of session-store and token-store components needed to keep information in the different Oauth communication flows. Following are the protocols descriptions:
All TokenStore implementations must provide temporary or persistent storage and must expire tokens that are no longer valid. Expiry policies are left to the implementor to decide. Token ids are determined by the client, but are recommended to be resistent to prediction and thus forgery (using HMAC, etc.).
A SessionStore maps an identifier, stored in a cookie, to a set of attributes. It is able to get cookies from the HTTP request, and set them on the HTTP response. A SessionStore will typically wrap a TokenStore.
Due that all session-store need a token-store to maintain related data, let's remove from our visualisation all those obvius token-store components (highlighted in orange). On the other hand, let's do the same with listener and router component relations(hightlighted in yellow) removing listeners from visualisation
- :authorization-listener webservices components: [:authorization-server, :reset-password, :signup-form :login :logout]
- :http-listener webservices components: [:bootstrap-cover-website-website :webapp-oauth-client].
Reference implementation cylon.oauth.client.web-client/WebClient
WebClient Protocols ;================== modular.bidi/WebService (routes): + :get "/grant" + :get "/logout" cylon.oauth.client/AccessTokenGrantee (solicit-access-token [_ req uri] [_ req uri scope-korks] "Initiate a process (typically via a HTTP redirect) that will result in a new request being made with an access token, if possible. Don't request specific scopes but get the defaults for the client.") (expired? [_ req access-token]) (refresh-access-token [_ req] "Initiate a process (typically via a HTTP redirect) that will result in a new request being made with an access token, if possible." )) cylon.authentication.protocols/RequestAuthenticator (authenticate [_ request] "Return (as a map) any credentials that can be determined from the given Ring request")
Then, :webapp-oauth-client besides behaving as an independet modular.bidi/WebService connected to its related :router, and responsing to
get calls it also accomplish cylon.oauth.client/AccessTokenGrantee for soliciting access token, validating token and refreshing tokens.
Also our client side can use the user identity obtained from auth-server to restrict client data using cylon.authentication.protocols/RequestAuthenticator protocol. Please be aware that client data is not user data so the way that client authenticate its own data is responsability of client and is outside of OAuht2 spec. Anyway juxt/cylon provides a practical middleware to protect client resources based in the client-session-store component and the identity obtained once user is logged with the auth-server. You can see how to use it this middleware on the demo project
cylon.oauth.server.server/AuthorizationServer AuthorizationServer Protocols implemented ;======================================== modular.bidi/WebService (routes): + :get "/authorize" + :post "/permit-client" + :post "/access-token" cylon.authentication.protocols/RequestAuthenticator (authenticate [_ request] "Return (as a map) any credentials that can be determined from the given Ring request")
TODO: in progress