Tp7.ggn>EE&rhnOzdt1
+```
+
+**Important note:** if no certificate is provided, Hydra uses self-signed TLS certificates for HTTPS. This should
+never be done in production. To skip the TLS verification step on the client, provide the `--skip-tls-verify` flag.
+
+Why not issue an access token for your client?
+
+```
+$ hydra token client --skip-tls-verify
+JLbnRS9GQmzUBT4x7ESNw0kj2wc0ffbMwOv3QQZW4eI.qkP-IQXn6guoFew8TvaMFUD-SnAyT8GmWuqGi3wuWXg
+```
+
+Let's try this with the authorize code grant!
+
+```
+$ hydra token user --skip-tls-verify
+If your browser does not open automatically, navigate to: https://192.168.99.100:4444/oauth2/...
+Setting up callback listener on http://localhost:4445/callback
+Press ctrl + c on Linux / Windows or cmd + c on OSX to end the process.
+```
+
+Great! You installed hydra, connected the CLI, created a client and completed two authentication flows!
diff --git a/docs/dist/gliffy/hydra-arch.gliffy b/docs/dist/gliffy/hydra-arch.gliffy
new file mode 100644
index 00000000000..298c127bdc4
--- /dev/null
+++ b/docs/dist/gliffy/hydra-arch.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":373,"y":328,"rotation":0,"id":55,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":55,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-3,2],[122,2],[122,59.5],[247,59.5]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"px":1,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":42,"px":0,"py":0.5}}},"linkMap":[]},{"x":329.79999999999995,"y":207.5,"rotation":0,"id":48,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":80,"height":30,"lockAspectRatio":false,"lockShape":false,"order":48,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.5999999999999999,"y":0,"rotation":0,"id":50,"uid":null,"width":76.8,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Docker
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":250,"y":222.5,"rotation":0,"id":46,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":140,"height":200,"lockAspectRatio":false,"lockShape":false,"order":0,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":325,"y":382,"rotation":0,"id":30,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":16,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-5,-2],[-5,105.5],[-195,105.5]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":31,"uid":null,"width":123,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"snapshots for recovery
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":11,"px":1,"py":0.5}}},"linkMap":[]},{"x":80,"y":367,"rotation":0,"id":27,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":14,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":false,"interpolationType":"linear","cornerRadius":null,"controlPath":[[0,0.5],[0,83]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":32,"uid":null,"width":93,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"create snapshots
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":16,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":11,"px":0.5,"py":0}}},"linkMap":[]},{"x":197,"y":321,"rotation":0,"id":22,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":12,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[73,9],[26.333333333333314,9],[-20.333333333333343,9],[-67,9]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":23,"uid":null,"width":44,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"pub/sub
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"px":0,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":16,"px":1,"py":0.5}}},"linkMap":[]},{"x":30,"y":292.5,"rotation":0,"id":16,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":10,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":18,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Message Broker
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":30,"y":450,"rotation":0,"id":11,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":8,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":13,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Datastore
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":617,"y":332,"rotation":0,"id":8,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":6,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":1,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[3,-72.90620433565948],[-122,-72.90620433565948],[-122,-2],[-247,-2]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":9,"uid":null,"width":66,"height":28,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"HTTP REST\n
OAuth2
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":42,"px":1.1102230246251563e-16,"py":0.2928932188134525}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"px":1,"py":0.5}}},"linkMap":[]},{"x":270,"y":280,"rotation":0,"id":2,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.multiple_documents","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":4,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.multiple_documents.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":4,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Hydra Host
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":810,"y":62.5,"rotation":0,"id":51,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":70,"height":40,"lockAspectRatio":false,"lockShape":false,"order":51,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":53,"uid":null,"width":66,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Clients
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":665,"y":540,"rotation":0,"id":40,"uid":"com.gliffy.shape.network.network_v3.business.server","width":77,"height":120,"lockAspectRatio":true,"lockShape":false,"order":20,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.server_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":665,"y":387.5,"rotation":0,"id":38,"uid":"com.gliffy.shape.network.network_v3.business.workstation_lcd","width":120,"height":120,"lockAspectRatio":true,"lockShape":false,"order":19,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.workstation_lcd_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":665,"y":90,"rotation":0,"id":36,"uid":"com.gliffy.shape.network.network_v3.home.laptop","width":120,"height":120,"lockAspectRatio":true,"lockShape":false,"order":18,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.laptop_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":665,"y":240,"rotation":0,"id":5,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.window","width":120,"height":100,"lockAspectRatio":false,"lockShape":false,"order":2,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.window.ui_v2","strokeWidth":2,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":1.3333333333333333,"y":0,"rotation":0,"id":7,"uid":null,"width":117.33333333333336,"height":70,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"top","overflow":"none","vposition":"none","hposition":"none","html":"\n
\n
\n
\n
Hydra CLI
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":620,"y":77.5,"rotation":0,"id":42,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":210,"height":620,"lockAspectRatio":false,"lockShape":false,"order":1,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]}],"background":"#FFFFFF","width":880,"height":698,"maxWidth":5000,"maxHeight":5000,"nodeIndex":57,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2},"com.gliffy.shape.flowchart.flowchart_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2},"com.gliffy.shape.network.network_v3.home":{"fill":"#003366"},"com.gliffy.shape.network.network_v3.business":{"fill":"#003366"}},"lineStyles":{"global":{"startArrow":1,"endArrow":1}},"textStyles":{},"themeData":null}}
\ No newline at end of file
diff --git a/docs/dist/gliffy/hydra.gliffy b/docs/dist/gliffy/hydra.gliffy
new file mode 100644
index 00000000000..7b8f95a92f2
--- /dev/null
+++ b/docs/dist/gliffy/hydra.gliffy
@@ -0,0 +1 @@
+{"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":651.5405032467534,"y":529,"rotation":0,"id":120,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":239.4375,"height":70,"lockAspectRatio":false,"lockShape":false,"order":120,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":122,"uid":null,"width":235.4375,"height":56,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"You can use Hydra with any Identity Provider. You must implement the capability to issue consent tokens using JWT.
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":907.45,"y":224,"rotation":0,"id":117,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":110,"height":40,"lockAspectRatio":false,"lockShape":false,"order":117,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":119,"uid":null,"width":106,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Identity Provider
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":178.45000000000005,"y":273,"rotation":0,"id":112,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":112,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[2.137084989847608,2],[2.137084989847608,-90.5],[114,-90.5],[114,-163]],"lockSegments":{"1":true}}},"children":[{"x":0,"y":0,"rotation":0,"id":113,"uid":null,"width":157,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"5. Receive Access / ID Token
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":93,"px":0.7071067811865476,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":4,"px":0.5,"py":1}}},"linkMap":[]},{"x":418.01250000000005,"y":241.5,"rotation":0,"id":103,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":500,"height":380,"lockAspectRatio":false,"lockShape":false,"order":0,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":760.45,"y":415,"rotation":0,"id":99,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":42,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[157.5625,-62.20057685088807],[197.5625,-62.20057685088807],[197.5625,270],[-613,270],[-613,230]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":100,"uid":null,"width":177,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"4. Return Signed Consent Token
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":103,"px":1,"py":0.29289321881345237}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":93,"px":0.5,"py":1}}},"linkMap":[]},{"x":0.537500000000037,"y":630,"rotation":0,"id":95,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":133.82500000000002,"height":30,"lockAspectRatio":false,"lockShape":false,"order":40,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.6765000000000003,"y":0,"rotation":0,"id":97,"uid":null,"width":128.47200000000004,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Authorization Service
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":67.45000000000005,"y":275,"rotation":0,"id":93,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","width":160,"height":370,"lockAspectRatio":false,"lockShape":false,"order":1,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":221.45000000000005,"y":257,"rotation":0,"id":36,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":16,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[6,126.37049096097735],[101.28125,126.37049096097735],[101.28125,95.79942314911193],[196.56250000000006,95.79942314911193]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":57,"uid":null,"width":140,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"2. Issue Consent Request
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":93,"px":1,"py":0.29289321881345237}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":103,"px":1.1102230246251563e-16,"py":0.2928932188134525}}},"linkMap":[]},{"x":242.45000000000005,"y":76,"rotation":0,"id":34,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":12,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[5,-11],[-128.1370849898476,-11],[-128.1370849898476,199]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":55,"uid":null,"width":137,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"1. Request Access Token
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":4,"px":0,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":93,"px":0.2928932188134524,"py":0}}},"linkMap":[]},{"x":148.2,"y":513,"rotation":0,"id":38,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":18,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-4.374999999999943,-21.44108899330365],[-4.374999999999943,-58.62739266220245],[-4.374999999999972,-95.8136963311012],[-4.374999999999972,-133]],"lockSegments":{}}},"children":[],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":26,"px":0.5,"py":0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":23,"px":0.5,"py":1}}},"linkMap":[]},{"x":87.4500000000001,"y":491.5,"rotation":0,"id":26,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":120,"height":130,"lockAspectRatio":false,"lockShape":false,"order":8,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.4,"y":0,"rotation":0,"id":39,"uid":null,"width":115.19999999999999,"height":112,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"\n
\n
Hydra Database (Rethink DB)\n
\n
Client Credentials, Access Tokens, Policies, ...
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":77.44999999999999,"y":290,"rotation":0,"id":23,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.multiple_documents","width":132.75000000000006,"height":90,"lockAspectRatio":false,"lockShape":false,"order":6,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.multiple_documents.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.655,"y":0,"rotation":0,"id":66,"uid":null,"width":127.44000000000005,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Hydra Host
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":385.08149350649353,"y":-52,"rotation":0,"id":58,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":25,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-47.631493506493484,117],[282.9310064935065,117],[282.9310064935065,293.5]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":114,"uid":null,"width":117,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"3. Login and Consent
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":4,"px":1,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":103,"px":0.5,"py":0}}},"linkMap":[]},{"x":247.45000000000005,"y":20,"rotation":0,"id":4,"uid":"com.gliffy.shape.network.network_v3.home.laptop","width":90,"height":90,"lockAspectRatio":true,"lockShape":false,"order":4,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.laptop_3d.network_v3","strokeWidth":2,"strokeColor":"#000000","fillColor":"#003366","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":559.6967532467534,"y":425,"rotation":0,"id":81,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":38,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[-4.368506493506516,-1],[-4.368506493506516,32.333333333333314],[-4.368506493506516,65.66666666666669],[-4.368506493506516,99]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":83,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"Verify Credentials
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":29,"px":0.5,"py":1}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":78,"px":0.5,"py":0}}},"linkMap":[]},{"x":505.32824675324673,"y":524,"rotation":0,"id":78,"uid":"com.gliffy.shape.flowchart.flowchart_v1.default.database","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":36,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.database.flowchart_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":80,"uid":null,"width":96,"height":28,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"LDAP, MySQL, Google, ...
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":654.6967532467534,"y":346,"rotation":0,"id":76,"uid":"com.gliffy.shape.basic.basic_v1.default.line","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":35,"graphic":{"type":"Line","Line":{"strokeWidth":2,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":1,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":10,"controlPath":[[0.6314935064934843,-2],[9.087662337662323,-2],[17.54383116883116,-2],[26,-2]],"lockSegments":{}}},"children":null,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":29,"px":1,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":67,"px":0,"py":0.5}}},"linkMap":[]},{"x":691.2592532467534,"y":304,"rotation":0,"id":72,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.text_area","width":178.875,"height":50,"lockAspectRatio":false,"lockShape":false,"order":33,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2.9812500000000006,"y":0,"rotation":0,"id":74,"uid":null,"width":172.91249999999994,"height":42,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"top","overflow":"none","vposition":"none","hposition":"none","html":"Do you want to grant app foo access to your pictures and email?
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":790.1342532467534,"y":374,"rotation":0,"id":70,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.button","width":80,"height":20,"lockAspectRatio":false,"lockShape":false,"order":31,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#DADADA","gradient":true,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":71,"uid":null,"width":76,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Deny\n
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":691.2592532467534,"y":374,"rotation":0,"id":68,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.button","width":80,"height":20,"lockAspectRatio":false,"lockShape":false,"order":29,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#DADADA","gradient":true,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":69,"uid":null,"width":76,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Allow
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":680.6967532467534,"y":264,"rotation":0,"id":67,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.window","width":200,"height":160,"lockAspectRatio":false,"lockShape":false,"order":28,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.window.ui_v2","strokeWidth":2,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]},{"x":495.32824675324684,"y":344,"rotation":0,"id":44,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.textbox","width":120,"height":20,"lockAspectRatio":false,"lockShape":false,"order":23,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":47,"uid":null,"width":116,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Password
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":495.32824675324684,"y":319,"rotation":0,"id":42,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.textbox","width":120,"height":20,"lockAspectRatio":false,"lockShape":false,"order":21,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":46,"uid":null,"width":116,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"User
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":535.3282467532468,"y":374,"rotation":0,"id":40,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.button","width":80,"height":20,"lockAspectRatio":false,"lockShape":false,"order":19,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.round_rectangle.basic_v1","strokeWidth":2,"strokeColor":"#8D8D8D","fillColor":"#DADADA","gradient":true,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":41,"uid":null,"width":76,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"Log in
","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":455.32824675324684,"y":264,"rotation":0,"id":29,"uid":"com.gliffy.shape.ui.ui_v2.forms_components.window","width":200,"height":160,"lockAspectRatio":false,"lockShape":false,"order":2,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.window.ui_v2","strokeWidth":2,"strokeColor":"#000000","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[],"linkMap":[]}],"background":"#FFFFFF","width":1018,"height":692,"maxWidth":5000,"maxHeight":5000,"nodeIndex":124,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.network.network_v3.home":{"fill":"#003366"},"com.gliffy.shape.basic.basic_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2},"com.gliffy.shape.network.network_v3.business":{"fill":"#003366"},"com.gliffy.shape.flowchart.flowchart_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2}},"lineStyles":{"global":{"endArrow":1,"startArrow":0}},"textStyles":{},"themeData":null}}
\ No newline at end of file
diff --git a/docs/dist/images/abstract_flow.png b/docs/dist/images/abstract_flow.png
new file mode 100644
index 00000000000..f57769ced3a
Binary files /dev/null and b/docs/dist/images/abstract_flow.png differ
diff --git a/docs/dist/images/consent.png b/docs/dist/images/consent.png
new file mode 100644
index 00000000000..184dd779b4f
Binary files /dev/null and b/docs/dist/images/consent.png differ
diff --git a/docs/dist/images/google.png b/docs/dist/images/google.png
new file mode 100644
index 00000000000..9ccc6e6fd12
Binary files /dev/null and b/docs/dist/images/google.png differ
diff --git a/docs/dist/images/google2.png b/docs/dist/images/google2.png
new file mode 100644
index 00000000000..0e908b66968
Binary files /dev/null and b/docs/dist/images/google2.png differ
diff --git a/docs/dist/images/hydra-arch-warden.png b/docs/dist/images/hydra-arch-warden.png
new file mode 100644
index 00000000000..890dfb7e1b9
Binary files /dev/null and b/docs/dist/images/hydra-arch-warden.png differ
diff --git a/docs/dist/images/hydra-arch.png b/docs/dist/images/hydra-arch.png
new file mode 100644
index 00000000000..90d396d48b4
Binary files /dev/null and b/docs/dist/images/hydra-arch.png differ
diff --git a/docs/dist/images/hydra-authentication.gif b/docs/dist/images/hydra-authentication.gif
new file mode 100644
index 00000000000..b6f789deebd
Binary files /dev/null and b/docs/dist/images/hydra-authentication.gif differ
diff --git a/docs/dist/images/login-success-a.gif b/docs/dist/images/login-success-a.gif
new file mode 100644
index 00000000000..7e5c3298682
Binary files /dev/null and b/docs/dist/images/login-success-a.gif differ
diff --git a/dist/logo.png b/docs/dist/images/logo.png
similarity index 100%
rename from dist/logo.png
rename to docs/dist/images/logo.png
diff --git a/dist/monitoring.gif b/docs/dist/images/monitoring.gif
similarity index 100%
rename from dist/monitoring.gif
rename to docs/dist/images/monitoring.gif
diff --git a/dist/oauth2-flow.gif b/docs/dist/images/oauth2-flow.gif
similarity index 100%
rename from dist/oauth2-flow.gif
rename to docs/dist/images/oauth2-flow.gif
diff --git a/dist/run-the-example.gif b/docs/dist/images/run-the-example.gif
similarity index 100%
rename from dist/run-the-example.gif
rename to docs/dist/images/run-the-example.gif
diff --git a/docs/dist/images/social-login-example.jpg b/docs/dist/images/social-login-example.jpg
new file mode 100644
index 00000000000..e3f737d5e29
Binary files /dev/null and b/docs/dist/images/social-login-example.jpg differ
diff --git a/docs/dist/images/social-login-example.png b/docs/dist/images/social-login-example.png
new file mode 100644
index 00000000000..10f1ea262c9
Binary files /dev/null and b/docs/dist/images/social-login-example.png differ
diff --git a/docs/dist/images/social-login.png b/docs/dist/images/social-login.png
new file mode 100644
index 00000000000..f4f4016daa4
Binary files /dev/null and b/docs/dist/images/social-login.png differ
diff --git a/docs/faq/consistency.md b/docs/faq/consistency.md
new file mode 100644
index 00000000000..e8e6bbd41ef
--- /dev/null
+++ b/docs/faq/consistency.md
@@ -0,0 +1,5 @@
+# Eventually consistent
+
+Using hydra with RethinkDB implies eventual consistency on all endpoints, except `/oauth2/auth` and `/oauth2/token`.
+Eventual consistent data is usually not immediately available. This is dependent on the network latency between Hydra
+and RethinkDB.
\ No newline at end of file
diff --git a/docs/faq/disable-https.md b/docs/faq/disable-https.md
new file mode 100644
index 00000000000..75c79e8ce6e
--- /dev/null
+++ b/docs/faq/disable-https.md
@@ -0,0 +1,3 @@
+# How can I disable HTTPS for testing?
+
+You can do so by running `hydra host --dangerous-force-http`.
\ No newline at end of file
diff --git a/docs/faq/http-api.md b/docs/faq/http-api.md
new file mode 100644
index 00000000000..25362e0302b
--- /dev/null
+++ b/docs/faq/http-api.md
@@ -0,0 +1,3 @@
+# Is there an HTTP API Documentation?
+
+Yes, it is available at [Apiary](http://docs.hdyra.apiary.io/).
diff --git a/docs/faq/https-tls-import.md b/docs/faq/https-tls-import.md
new file mode 100644
index 00000000000..97a2c8175ac
--- /dev/null
+++ b/docs/faq/https-tls-import.md
@@ -0,0 +1,18 @@
+# How can I import TLS certificates?
+
+You can import TLS certificates when running `hydra host`. This can be done by setting the following environment variables:
+
+**Read from file**
+- `HTTPS_TLS_CERT_PATH`: The path to the TLS certificate (pem encoded).
+- `HTTPS_TLS_KEY_PATH`: The path to the TLS private key (pem encoded).
+
+**Embedded**
+- `HTTPS_TLS_CERT`: A pem encoded TLS certificate passed as string. Can be used instead of TLS_CERT_PATH.
+- `HTTPS_TLS_KEY`: A pem encoded TLS key passed as string. Can be used instead of TLS_KEY_PATH.
+
+Or by specifying the following flags:
+
+```
+--https-tls-cert-path string Path to the certificate file for HTTP/2 over TLS (https). You can set HTTPS_TLS_KEY_PATH or HTTPS_TLS_KEY instead.
+--https-tls-key-path string Path to the key file for HTTP/2 over TLS (https). You can set HTTPS_TLS_KEY_PATH or HTTPS_TLS_KEY instead.
+```
\ No newline at end of file
diff --git a/docs/faq/log-level.md b/docs/faq/log-level.md
new file mode 100644
index 00000000000..4663f4eea9f
--- /dev/null
+++ b/docs/faq/log-level.md
@@ -0,0 +1,9 @@
+# Can I set the log level to warn, error, debug, ...?
+
+Yes, you can do so by setting the environment variable `LOG_LEVEL=`. There are various levels supported:
+
+* debug
+* warn
+* error
+* fatal
+* panic
\ No newline at end of file
diff --git a/docs/faq/oauth2-auth.md b/docs/faq/oauth2-auth.md
new file mode 100644
index 00000000000..3db3c1f765f
--- /dev/null
+++ b/docs/faq/oauth2-auth.md
@@ -0,0 +1,11 @@
+# Should I use OAuth2 tokens for authentication?
+
+OAuth2 tokens are like money. It allows you to buy stuff, but the cashier does not really care if the money is
+yours or if you stole it, as long as it's valid money. Depending on what you understand as authentication, this is a yes and no answer:
+
+* **Yes:** You can use access tokens to find out which user ("subject") is performing an action in a resource provider (blog article service, shopping basket, ...).
+Coming back to the money example: *You*, the subject, receives a cappuccino from the vendor (resource provider) in exchange for money (access token).
+* **No:** Never use access tokens for logging people in, for example `http://myapp.com/login?access_token=...`.
+Coming back to the money example: The police officer ("authentication server") will not accept money ("access token") as a proof of identity ("it's really you"). Unless he is corrupt ("vulnerable"), of course.
+
+In the second example ("authentication server"), you must use OpenID Connect ID Tokens.
\ No newline at end of file
diff --git a/docs/faq/oauth2-error.md b/docs/faq/oauth2-error.md
new file mode 100644
index 00000000000..ae51b974882
--- /dev/null
+++ b/docs/faq/oauth2-error.md
@@ -0,0 +1,4 @@
+# What will happen if an error occurs during an OAuth2 flow?
+
+The user agent will either, according to spec, be redirected to the OAuth2 client who initiated the request, if possible. If not, the user agent will be redirected to the identity provider
+endpoint and an `error` and `error_description` query parameter will be appended to it's URL.
diff --git a/docs/faq/redirect-uri.md b/docs/faq/redirect-uri.md
new file mode 100644
index 00000000000..05ec1b05435
--- /dev/null
+++ b/docs/faq/redirect-uri.md
@@ -0,0 +1,4 @@
+# Why isn't the redirect url working?
+
+Hydra enforces HTTPS for all hosts except localhost. Also make sure that the path is an exact match. `http://localhost:123/`
+is not the same as `http://localhost:123`.
\ No newline at end of file
diff --git a/docs/faq/rethink-ca.md b/docs/faq/rethink-ca.md
new file mode 100644
index 00000000000..dccfce1b559
--- /dev/null
+++ b/docs/faq/rethink-ca.md
@@ -0,0 +1,12 @@
+# How can I import a custom CA for RethinkDB?
+
+You can do so by specifying environment variables:
+
+- `RETHINK_TLS_CERT_PATH`: The path to the TLS certificate (pem encoded) used to connect to rethinkdb.
+- `RETHINK_TLS_CERT`: A pem encoded TLS certificate passed as string. Can be used instead of `RETHINK_TLS_CERT_PATH`.
+
+or via command line flag:
+
+```
+--rethink-tls-cert-path string Path to the certificate file to connect to rethinkdb over TLS (https). You can set RETHINK_TLS_CERT_PATH or RETHINK_TLS_CERT instead.
+```
\ No newline at end of file
diff --git a/docs/faq/when-use.md b/docs/faq/when-use.md
new file mode 100644
index 00000000000..30c62b53cb0
--- /dev/null
+++ b/docs/faq/when-use.md
@@ -0,0 +1,34 @@
+# How do I know if OAuth2 / Hydra is the right choice for me?
+
+OAuth2 and OpenID Connect are tricky to understand. It is important to understand that OAuth2 is
+a delegation protocol. It makes sense to use Hydra in new and existing projects. A use case covering an existing project
+explains how one would use Hydra in a new one as well. So let's look at a use case!
+
+Let's assume we are running a ToDo List App (todo24.com). ToDo24 has a login endpoint (todo24.com/login).
+The login endpoint is written in node and uses MongoDB to store user information (email + password + settings). Of course,
+todo24 has other services as well: list management (todo24.com/lists/manage: close, create, move), item management (todo24.com/lists/items/manage: mark solved, add), and so on.
+You are using cookies to see which user is performing the request.
+
+Now you decide to use OAuth2 on top of your current infrastructure. There are many reasons to do this:
+* You want to open up your APIs to third-party developers. Their apps will be using OAuth2 Access Tokens to access a user's to do list.
+* You want a mobile client. Because you can not store secrets on devices (they can be reverse engineered and stolen), you use OAuth2 Access Tokens instead.
+* You have Cross Origin Requests. Making cookies work with Cross Origin Requests weakens or even disables important anti-CSRF measures.
+* You want to write an in-browser client. This is the same case as in a mobile client (you can't store secrets in a browser).
+
+These are only a couple of reasons to use OAuth2. You might decide to use OAuth2 as your single source of authorization, thus maintaining
+only one authorization protocol and being able to open up to third party devs in no time. With OpenID Connect, you are able to delegate authentication as well as authorization!
+
+Your decision is final. You want to use OAuth2 and you want Hydra to do the job. You install Hydra in your cluster using docker.
+Next, you set up some exemplary OAuth2 clients. Clients can act on their own, but most of the time they need to access a user's todo lists.
+To do so, the client initiates an OAuth2 request. This is where [Hydra's authentication flow](https://ory-am.gitbooks.io/hydra/content/oauth2.html#authentication-flow) comes in to play.
+Before Hydra can issue an access token, we need to know WHICH user is giving consent. To do so, Hydra redirects the user agent (e.g. browser, mobile device)
+to the login endpoint alongside with a challenge that contains an expiry time and other information. The login endpoint (todo24.com/login) authenticates the
+user as usual, e.g. by username & password, session cookie or other means. Upon successful authentication, the login endpoint asks for the user's consent:
+*"Do you want to grant MyCoolAnalyticsApp read & write access to all your todo lists? [Yes] [No]"*. Once the user clicks *Yes* and gives consent,
+the login endpoint redirects back to hydra and appends something called a *consent token*. The consent token is a cryptographically signed
+string that contains information about the user, specifically the user's unique id. Hydra validates the signature's trustworthiness
+and issues an OAuth2 access token and optionally a refresh or OpenID token.
+
+Every time a request containing an access token hits a resource server (todo24.com/lists/manage), you make a request to Hydra asking who the token's
+subject (the user who authorized the client to create a token on its behalf) is and whether the token is valid or not. You may optionally
+ask if the token has permission to perform a certain action.
\ No newline at end of file
diff --git a/docs/install.md b/docs/install.md
new file mode 100644
index 00000000000..13e96b93561
--- /dev/null
+++ b/docs/install.md
@@ -0,0 +1,51 @@
+# Installation
+
+There are various ways of installing hydra on your system.
+
+## Download binaries
+
+The client and server **binaries are downloadable at [releases](https://github.com/ory-am/hydra/releases)**.
+There is currently no installer available. You have to add the hydra binary to the PATH environment variable yourself or put
+the binary in a location that is already in your path (`/usr/bin`, ...).
+If you do not understand what that all of this means, ask in our [chat channel](https://gitter.im/ory-am/hydra). We are happy to help.
+
+## Using Docker
+
+**Starting the host** is easiest with docker. The host process handles HTTP requests and is backed by a database.
+Read how to install docker on [Linux](https://docs.docker.com/linux/), [OSX](https://docs.docker.com/mac/) or
+[Windows](https://docs.docker.com/windows/). Hydra is available on [Docker Hub](https://hub.docker.com/r/oryam/hydra/).
+
+You can use Hydra without a database, but be aware that restarting, scaling
+or stopping the container will **lose all data**:
+
+```
+$ docker run -d -p 4444:4444 oryam/hydra --name my-hydra
+ec91228cb105db315553499c81918258f52cee9636ea2a4821bdb8226872f54b
+```
+
+**Using the client command line interface** can be achieve by ssh'ing into the hydra container
+and execute the hydra command from there:
+
+```
+$ docker exec -i -t /bin/bash
+# e.g. docker exec -i -t ec91228 /bin/bash
+
+root@ec91228cb105:/go/src/github.com/ory-am/hydra# hydra
+Hydra is a twelve factor OAuth2 and OpenID Connect provider
+
+[...]
+```
+
+## Building from source
+
+If you wish to compile hydra yourself, you need to install and set up [Go 1.5+](https://golang.org/) and add `$GOPATH/bin`
+to your `$PATH`. To do so, run the following commands in a shell (bash, sh, cmd.exe, ...):
+
+```
+go get github.com/ory-am/hydra
+go get github.com/Masterminds/glide
+cd $GOPATH/src/github.com/ory-am/hydra
+glide install
+go install github.com/ory-am/hydra
+hydra
+```
diff --git a/docs/jwk.md b/docs/jwk.md
new file mode 100644
index 00000000000..1294ed64bf0
--- /dev/null
+++ b/docs/jwk.md
@@ -0,0 +1,44 @@
+# JSON Web Keys (JWK)
+
+A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key and is specified at [IETF RFC7517](https://tools.ietf.org/html/rfc7517). If you've heard of PEM files...
+
+```
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDg
+MBQGCCqGSIb3DQMHBAgD1kGN4ZslJgSCBMi1xk9jhlPxPc
+9g73NQbtqZwI+9X5OhpSg/2ALxlCCjbqvzgSu8gfFZ4yo+
+A .... MANY LINES LIKE THAT ....
+X0R+meOaudPTBxoSgCCM51poFgaqt4l6VlTN4FRpj+c/Wc
+blK948UAda/bWVmZjXfY4Tztah0CuqlAldOQBzu8TwE7WD
+H0ga/iLNvWYexG7FHLRiq5hTj0g9mUPEbeTXuPtOkTEb/0
+GEs=
+-----END ENCRYPTED PRIVATE KEY-----
+```
+
+... JWKs are the same, but formatted using JSON:
+
+```
+{
+ "keys":
+ [
+ {"kty":"oct",
+ "alg":"A128KW",
+ "k":"GawgguFyGrWKav7AX4VKUg"},
+
+ {"kty":"oct",
+ "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75
+ aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow",
+ "kid":"HMAC key used in JWS spec Appendix A.1 example"}
+ ]
+}
+```
+
+Hydra offers an API for generating and managing JWKs, the [JSON Web Keys API](http://docs.hdyra.apiary.io/#reference/json-web-keys-jwk).
+When using persistent storage backends, the keys are encrypted at rest using AES256-GCM and the **system secret**.
+The **system secret** is generated by default and overridden by the environment variable **SYSTEM_SECRET**.
+
+JWKs are well supported amongst all languages. An HTTPS API takes away the pain of managing
+certificates and keeps them in a safe place. **When transporting private keys over the network you
+MUST encrypt ALL related traffic.**
+
+Please read the [API Documentation](http://docs.hdyra.apiary.io/#reference/json-web-keys-jwk) for API details.
diff --git a/docs/oauth2.md b/docs/oauth2.md
new file mode 100644
index 00000000000..1712dfe10d8
--- /dev/null
+++ b/docs/oauth2.md
@@ -0,0 +1,4 @@
+# OAuth2
+
+This section covers some basic OAuth2 and OpenID Connect concepts and shows you how to integrate Hydra with your authentication
+flow.
diff --git a/docs/oauth2/basics.md b/docs/oauth2/basics.md
new file mode 100644
index 00000000000..bc383f48500
--- /dev/null
+++ b/docs/oauth2/basics.md
@@ -0,0 +1,48 @@
+# OAuth2 Basics
+
+[This introduction was taken from the Digital Ocean Blog.](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2)
+
+**OAuth 2** is an authorization framework that enables applications to obtain limited access to user accounts on an
+HTTP service, such as Facebook, GitHub, and Hydra. It works by delegating user authentication to the service that hosts
+the user account, and authorizing third-party applications to access the user account. OAuth 2 provides authorization
+flows for web and desktop applications, and mobile devices.
+
+![](../dist/images/abstract_flow.png)
+
+Here is a more detailed explanation of the steps in the diagram:
+
+* The application requests authorization to access service resources from the user
+* If the user authorized the request, the application receives an authorization grant
+* The application requests an access token from the authorization server (API) by presenting authentication of its own
+identity, and the authorization grant.
+* If the application identity is authenticated and the authorization grant is valid, the authorization server (API)
+issues an access token to the application. Authorization is complete.
+* The application requests the resource from the resource server (API) and presents the access token for authentication.
+* If the access token is valid, the resource server (API) serves the resource to the application.
+
+The actual flow of this process will differ depending on the authorization grant type in use, but this is the general idea.
+
+Read more on OAuth2 on [the Digital Ocean Blog](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2).
+We also recommend reading [API Security: Deep Dive into OAuth and OpenID Connect](http://nordicapis.com/api-security-oauth-openid-connect-depth/).
+
+**Glossary**
+* **The resource owner** is the user who authorizes an application to access their account. The application's access to
+the user's account is limited to the "scope" of the authorization granted (e.g. read or write access).
+* **Authorization Server (Hydra)** verifies the identity of the user and issues access tokens to the *client application*.
+* **Client** is the *application* that wants to access the user's account. Before it may do so, it must be authorized
+by the user.
+* **Identity Provider** contains a log in user interface and a database of all your users. To integrate Hydra,
+you must modify the Identity Provider. It mus be able to generate consent tokens and ask for the user's consent.
+* **User Agent** is usually the resource owner's browser.
+* **Consent Endpoint** is an app (e.g. NodeJS) that is able to receive consent challenges and create consent tokens.
+It must verify the identity of the user that is giving the consent. This can be achieved using Cookie Auth,
+HTTP Basic Auth, Login HTML Form, or any other mean of authentication. Upon authentication, the user must be asked
+if he consents to allowing the client access to his resources.
+
+## OAuth2 Clients
+
+We already covered some basic OAuth2 concepts [in the Introduction](introduction.html).
+You can manage *clients* using the cli or the HTTP REST API.
+
+* **CLI:** `hydra clients -h`
+* **REST:** Read the [API Docs](http://docs.hdyra.apiary.io/#reference/oauth2-clients)
diff --git a/docs/oauth2/consent.md b/docs/oauth2/consent.md
new file mode 100644
index 00000000000..d903d9e4131
--- /dev/null
+++ b/docs/oauth2/consent.md
@@ -0,0 +1,109 @@
+# Consent Flow
+
+Hydra does not ship user authentication. This is something you will have to solve yourself. Usually when you are looking
+at using OAuth2 for your app, you already have user authentication anyways.
+
+In abstract, a consent flow looks like this:
+
+![](../dist/images/consent.png)
+
+1. A *client* application (app in browser in laptop) requests an access token from a resource owner: `https://hydra.myapp.com/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=vboeidlizlxrywkwlsgeggff&nonce=tedgziijemvninkuotcuuiof`.
+2. Hydra generates a consent challenge and forwards the *user agent* (browser in laptop) to the *consent endpoint*: `https://login.myapp.com/?challenge=eyJhbGciOiJSUzI1N...`.
+3. The *consent endpoint* verifies the resource owner's identity (e.g. cookie, username/password login form, ...). The consent challenge is then decoded and the information extracted. It is used to show the consent screen: `Do you want to grant _my cool app_ access to all your private data? [Yes] [No]`
+4. When consent is given, the *consent endpoint* generates a consent token and redirects the user agent (browser in laptop) back to hydra: `https://hydra.myapp.com/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=vboeidlizlxrywkwlsgeggff&nonce=tedgziijemvninkuotcuuiof&consent=eyJhbGciOiJSU...`.
+5. Hydra validates the consent token and issues the access token to the *user agent*.
+
+## Detailed Example
+
+In this section we assume that hydra runs on `https://192.168.99.100:4444` and our consent app on `https:/192.168.99.100:3000`.
+
+All user-based OAuth2 requests, including the OpenID Connect workflow, begin at the `/oauth2/auth` endpoint. For example: `https://192.168.99.100:4444/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=wewuphkgywhtldsmainefkyx&nonce=uqfjjzftqpjccdvxltaposri`
+
+If the request includes a valid redirect uri and a valid client id, hydra redirects the user to then consent url. The consent url can be set using the `$CONSENT_URL` environment variable.
+
+Let's set the `$CONSENT_URL` to `https:/192.168.99.100:3000/consent`, where a NodeJS application is running (the *consent app*). Next, Hydra appends a consent challenge to the consent url and redirects the user to it. For example: `http://192.168.99.100:3000/?challenge=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTU0ODIsImp0aSI6IjNmYWRlN2NjLTdlYTItNGViMi05MGI1LWY5OTUwNTI4MzgyOSIsInJlZGlyIjoiaHR0cHM6Ly8xOTIuMTY4Ljk5LjEwMDo0NDQ0L29hdXRoMi9hdXRoP2NsaWVudF9pZD1jM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NThcdTAwMjZyZXNwb25zZV90eXBlPWNvZGVcdTAwMjZzY29wZT1jb3JlK2h5ZHJhXHUwMDI2c3RhdGU9d2V3dXBoa2d5d2h0bGRzbWFpbmVma3l4XHUwMDI2bm9uY2U9dXFmamp6ZnRxcGpjY2R2eGx0YXBvc3JpIiwic2NwIjpbImNvcmUiLCJoeWRyYSJdfQ.KpLBotIEE4izVSAjLOeCCfm_wYZ7UWSCA81akr6Ci1yycKs8e_bhBYdSThy8JW3bAvofNcZ0v48ov9KxZVegWm8GuNbBEcNvKeiyW_8PiJXWE92YsMv-tDIL3VFPOp0469FmDLsSg5ohsFj5S89FzykNYfVxLPBAFcAS_JElWbo`
+
+The consent challenge is a signed RSA-SHA 256 (RS256) [JSON Web Token](https://tools.ietf.org/html/rfc7519) and contains the following claims:
+
+
+```
+eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTUwOTksImp0aSI6IjY0YzRmNzllLWUwMTYtNDViOC04YzBlLWQ5NmM2NzFjMWU4YSIsInJlZGlyIjoiaHR0cHM6Ly8xOTIuMTY4Ljk5LjEwMDo0NDQ0L29hdXRoMi9hdXRoP2NsaWVudF9pZD1jM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NThcdTAwMjZyZXNwb25zZV90eXBlPWNvZGVcdTAwMjZzY29wZT1jb3JlK2h5ZHJhXHUwMDI2c3RhdGU9bXlobnhxbXd6aHRleWN3ZW92Ymxzd3dqXHUwMDI2bm9uY2U9Z21tc3V2dHNidG9ldW1lb2hlc3p0c2hnIiwic2NwIjpbImNvcmUiLCJoeWRyYSJdfQ.v4K1-AuT5Uwu1DRNvdf7SwjjPT8KO97thRYa3pDWzjBLyjkCNvgp0P5V0oA3XqRutoFpYx4AtQyz0bY7n3XcPE7ZQ2nBWTBnZ04GzWbxcJNFhBvgc_jiQBECebdxN29kgxHoU0frtVDcz6Uur468nBa9D_BDBpN-KgEBsI5Hjhc
+
+{
+ "aud": "c3b49cf0-88e4-4faa-9489-28d5b8957858",
+ "exp": 1464515099,
+ "jti": "64c4f79e-e016-45b8-8c0e-d96c671c1e8a",
+ "redir": "https://192.168.99.100:4444/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=myhnxqmwzhteycweovblswwj&nonce=gmmsuvtsbtoeumeohesztshg",
+ "scp": [
+ "core",
+ "hydra"
+ ]
+}
+```
+
+The challenge claims are:
+* **jti:** A unique id.
+* **scp:** The requested scopes, e.g. `["blog.readall", "blog.writeall"]`
+* **aud:** The client id that initiated the request. You can fetch client data using the [OAuth2 Client API](http://docs.hdyra.apiary.io/#reference/oauth2/manage-the-oauth2-client-collection).
+* **exp:** The challenge's expiry date. Consent endpoints must not accept challenges that have expired.
+* **redir:** Where the consent endpoint should redirect the user agent to, once consent is given.
+
+Hydra signs the consent token with a key called consent.challenge.
+The public key can be looked up via the [Key Manager](https://ory-am.gitbooks.io/hydra/content/jwk.html):
+
+```
+https://192.168.99.100:4444/keys/consent.challenge/public
+```
+
+Next, the consent-app must check if the user is authenticated. This can be done by e.g. using a session cookie.
+If the user is not authenticate, he must be challenged to provide valid credentials through e.g. a HTML form.
+The consent-app could use LDAP, MySQL, RethinkDB or any other backend to store and verify the credentials.
+
+Upon user authentication, the consent-app must ask for the user's consent. This could look like:
+
+> _That super useful service app_ would like to:
+> * Know who you are
+> * View your extended profile info
+> * Get read access to all your cloud pictures
+>
+> [Deny] - [Allow]
+
+If the user clicks *Allow*, the consent-app redirects him back to the *redir* claim value. The consent-app appends
+a signed consent token to the URL:
+
+```
+https://192.168.99.100:4444/oauth2/auth?client_id=c3b49cf0-88e4-4faa-9489-28d5b8957858&response_type=code&scope=core+hydra&state=myhnxqmwzhteycweovblswwj&nonce=gmmsuvtsbtoeumeohesztshg&consent=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTUwOTksInNjcCI6WyJjb3JlIiwiaHlkcmEiXSwic3ViIjoiam9obi5kb2VAbWUuY29tIiwiaWF0IjoxNDY0NTExNTE1fQ.tX5TKdP9hHCgPbqBzKIYMjJVwqOdxf5ACScmQ6t20Qteo8AYEfavGwq8KxRF1Oz_otcQDdZY--jcl1caom0yT2eTvj1d9E2Hs7eXmYuW_xF9pTpmDwJnrcOlONFKsNZN97n41qprzMrsX5ez0T5AcopGwpPMxKhwGDSXq9CQgQU
+```
+
+The consent token is a RSA-SHA 256 (RS256) signed [JSON Web Token](https://tools.ietf.org/html/rfc7519)
+that contains the following claims:
+
+```
+eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJjM2I0OWNmMC04OGU0LTRmYWEtOTQ4OS0yOGQ1Yjg5NTc4NTgiLCJleHAiOjE0NjQ1MTUwOTksInNjcCI6WyJjb3JlIiwiaHlkcmEiXSwic3ViIjoiam9obi5kb2VAbWUuY29tIiwiaWF0IjoxNDY0NTExNTE1fQ.tX5TKdP9hHCgPbqBzKIYMjJVwqOdxf5ACScmQ6t20Qteo8AYEfavGwq8KxRF1Oz_otcQDdZY--jcl1caom0yT2eTvj1d9E2Hs7eXmYuW_xF9pTpmDwJnrcOlONFKsNZN97n41qprzMrsX5ez0T5AcopGwpPMxKhwGDSXq9CQgQU
+
+{
+ "aud": "c3b49cf0-88e4-4faa-9489-28d5b8957858",
+ "exp": 1464515099,
+ "scp": [
+ "core",
+ "hydra"
+ ],
+ "sub": "john.doe@me.com",
+ "iat": 1464511515,
+ "id_ext": { "foo": "bar" },
+ "at_ext": { "baz": true }
+}
+```
+
+The consent claims are:
+* **jti:** A unique id.
+* **scp:** The scopes the user opted in to *grant* access to, e.g. only `["blog.readall"]`.
+* **aud:** The client id that initiated the OAuth2 request. You can fetch client data using the [OAuth2 Client API](http://docs.hdyra.apiary.io/#reference/oauth2/manage-the-oauth2-client-collection).
+* **exp:** The expiry date of this token. Use very short lifespans (< 5 min).
+* **iat:** The tokens issuance time.
+* **id_ext:** If set, pass this extra data to the id token *(optional)*
+* **at_ext:** If set, pass this extra data to the access token session. You can retrieve the data by using the warden endpoints *(optional)*.
+
+Hydra validates the consent token with consent-app's public key. The public key must be stored in the (https://ory-am.gitbooks.io/hydra/content/key_manager.html) at `https://localhost:4444/keys/consent.endpoint/public`
+
+If you want, you can use the Key Manager to store and retrieve private keys as well. When Hydra boots for the first time, a private/public `consent.endpoint` keypair is created. You can that keypair to sign consent tokens. The private key is available at `https://localhost:4444/keys/asymmetric/consent.endpoint/private`.
diff --git a/docs/oauth2/openid.md b/docs/oauth2/openid.md
new file mode 100644
index 00000000000..f9ff3a48c53
--- /dev/null
+++ b/docs/oauth2/openid.md
@@ -0,0 +1,17 @@
+# OpenID Connect 1.0
+
+**OpenID Connect 1.0** is a simple identity layer on top of the OAuth 2.0 protocol.
+It allows Clients to verify the identity of the End-User based on the authentication performed
+by an Authorization Server, as well as to obtain basic profile information about the End-User in an
+interoperable and REST-like manner.
+
+OpenID Connect allows clients of all types, including Web-based, mobile, and JavaScript clients,
+to request and receive information about authenticated sessions and end-users. The specification
+suite is extensible, allowing participants to use optional features such as encryption of identity data,
+discovery of OpenID Providers, and session management, when it makes sense for them.
+
+There are different work flows for OpenID Connect 1.0, we recommend checking out the OpenID Connect sandbox at
+[openidconnect.net](https://openidconnect.net/).
+
+In a nutshell, add `openid` to the OAuth2 scope when making an OAuth2 Authorize Code request.
+You will receive an `id_token` alongside the `access_token` when making the code exchange.
\ No newline at end of file
diff --git a/docs/overview.md b/docs/overview.md
new file mode 100644
index 00000000000..bf25e726ff4
--- /dev/null
+++ b/docs/overview.md
@@ -0,0 +1,3 @@
+# Overview
+
+This section gives you a high level overview of Hydra's core concepts and its architecture.
\ No newline at end of file
diff --git a/docs/sdk.md b/docs/sdk.md
new file mode 100644
index 00000000000..4475a22f25a
--- /dev/null
+++ b/docs/sdk.md
@@ -0,0 +1,3 @@
+# SDK
+
+An SDK is available for Golang via `go get github.com/ory-am/hydra/sdk`. We are planning on implementing SDKs for other languages too.
diff --git a/docs/sdk/go.md b/docs/sdk/go.md
new file mode 100644
index 00000000000..fbad7141233
--- /dev/null
+++ b/docs/sdk/go.md
@@ -0,0 +1,120 @@
+## Go SDK
+
+Connect the SDK to Hydra:
+```go
+import "github.com/ory-am/hydra/sdk"
+
+hydra, err := sdk.Connect(
+ sdk.ClientID("client-id"),
+ sdk.ClientSecret("client-secret"),
+ sdk.ClustURL("https://localhost:4444"),
+)
+```
+
+Manage OAuth Clients using [`ory-am/hydra/client.HTTPManager`](/client/manager_http.go):
+
+```go
+import "github.com/ory-am/hydra/client"
+
+// Create a new OAuth2 client
+newClient, err := hydra.Client.CreateClient(&client.Client{
+ ID: "deadbeef",
+ Secret: "sup3rs3cret",
+ RedirectURIs: []string{"http://yourapp/callback"},
+ // ...
+})
+
+// Retrieve newly created client
+newClient, err = hydra.Client.GetClient(newClient.ID)
+
+// Remove the newly created client
+err = hydra.Client.DeleteClient(newClient.ID)
+
+// Retrieve list of all clients
+clients, err := hydra.Client.GetClients()
+```
+
+Manage SSO Connections using [`ory-am/hydra/connection.HTTPManager`](connection/manager_http.go):
+```go
+import "github.com/ory-am/hydra/connection"
+
+// Create a new connection
+newSSOConn, err := hydra.SSO.Create(&connection.Connection{
+ Provider: "login.google.com",
+ LocalSubject: "bob",
+ RemoteSubject: "googleSubjectID",
+})
+
+// Retrieve newly created connection
+ssoConn, err := hydra.SSO.Get(newSSOConn.ID)
+
+// Delete connection
+ssoConn, err := hydra.SSO.Delete(newSSOConn.ID)
+
+// Find a connection by subject
+ssoConns, err := hydra.SSO.FindAllByLocalSubject("bob")
+ssoConns, err := hydra.SSO.FindByRemoteSubject("login.google.com", "googleSubjectID")
+```
+
+Manage policies using [`ory-am/hydra/policy.HTTPManager`](policy/manager_http.go):
+```go
+import "github.com/ory-am/ladon"
+
+// Create a new policy
+// allow user to view his/her own photos
+newPolicy, err := hydra.Policy.Create(&ladon.DefaultPolicy{
+ ID: "1234", // ID is not required
+ Subjects: []string{"bob"},
+ Resources: []string{"urn:media:images"},
+ Actions: []string{"get", "find"},
+ Effect: ladon.AllowAccess,
+ Conditions: ladon.Conditions{
+ "owner": &ladon.EqualSubjectCondition{},
+ }
+})
+
+// Retrieve a stored policy
+policy, err := hydra.Policy.Get("1234")
+
+// Delete a policy
+err := hydra.Policy.Delete("1234")
+
+// Retrieve all policies for a subject
+policies, err := hydra.Policy.FindPoliciesForSubject("bob")
+```
+
+Manage JSON Web Keys using [`ory-am/hydra/jwk.HTTPManager`](jwk/manager_http.go):
+
+```go
+// Generate new key set
+keySet, err := hydra.JWK.CreateKeys("app-tls-keys", "HS256")
+
+// Retrieve key set
+keySet, err := hydra.JWK.GetKeySet("app-tls-keys")
+
+// Delete key set
+err := hydra.JWK.DeleteKeySet("app-tls-keys")
+```
+
+Validate requests with the Warden, uses [`ory-am/hydra/warden.HTTPWarden`](warden/warden_http.go):
+
+```go
+import "github.com/ory-am/ladon"
+
+func anyHttpHandler(w http.ResponseWriter, r *http.Request) {
+ // Check if a token is valid and is allowed to operate given scopes
+ ctx, err := firewall.TokenValid(context.Background(), firewall.TokenFromRequest(r), "photos", "files")
+ fmt.Sprintf("%s", ctx.Subject)
+
+ // Check if a token is valid and the token's subject fulfills the policy based access request.
+ ctx, err := firewall.TokenAllowed(context.Background(), "access-token", &ladon.Request{
+ Resource: "matrix",
+ Action: "create",
+ Context: ladon.Context{},
+ }, "photos", "files")
+ fmt.Sprintf("%s", ctx.Subject)
+}
+
+// Check if request is authorized
+hydra.Warden.HTTPAuthorized(ctx, req, "media.images")
+```
\ No newline at end of file
diff --git a/docs/sso.md b/docs/sso.md
new file mode 100644
index 00000000000..c83b76b00c4
--- /dev/null
+++ b/docs/sso.md
@@ -0,0 +1,39 @@
+# Social Login Management
+
+> Social login, also known as social sign-in, is a form of single sign-on using existing login information from a social
+networking service such as Facebook, Twitter or Google+ to sign into a third party website instead of creating
+a new login account specifically for that website. It is designed to simplify logins for end users as well as
+provide more and more reliable demographic information to web developers. *- [Source: Wikipedia](https://en.wikipedia.org/wiki/Social_login)*
+
+It is important to note, that Hydra supports you in managing Social Login capabilities,
+but does not handle Social Login itself.
+
+## Exemplary Social Login Journey
+
+The log in screen
+
+![](dist/images/social-login-example.jpg)
+
+Logging in with Google Account
+![](dist/images/google.png)
+
+User authorizes access
+![](dist/images/google2.png)
+
+![](dist/images/social-login-example.jpg)
+
+Login completed
+![](dist/images/login-success-a.gif)
+
+## In The Background
+
+Depending on the third party's APIs you complete the sign in request with OAuth 1.0,
+OAuth2, OpenID Connect, or some other flow. In any case, you will receive (e.g. /userinfo, id token, ...)
+a user id from that service, e.g. `googleuser:u398fjka8f2hj28g`. We call this value the **remote subject**,
+the login provider (e.g. Google) **provider**, and the users stored in your private MySQL/LDAP/...
+database **local subjects**.
+
+You can pass the provider and the remote subject values to the
+[Social Login API](http://docs.hdyra.apiary.io/#reference/social-login-management) and look up if one of your local
+subjects is linked to that third party account. If there is a match you can use the local subject value
+to identify and authenticate the user. If there is no match, you will probably send him to your sign up page.
\ No newline at end of file
diff --git a/firewall/warden.go b/firewall/warden.go
index 2449d7f8532..1c22507ab71 100644
--- a/firewall/warden.go
+++ b/firewall/warden.go
@@ -13,37 +13,35 @@ import (
type Context struct {
// Subject is the identity that authorized issuing the token, for example a user or an OAuth2 app.
// This is usually a uuid but you can choose a urn or some other id too.
- Subject string `json:"sub"`
+ Subject string `json:"sub"`
// GrantedScopes is a list of scopes that the subject authorized when asked for consent.
- GrantedScopes []string `json:"scopes"`
+ GrantedScopes []string `json:"scopes"`
// Issuer is the id of the issuer, typically an hydra instance.
- Issuer string `json:"iss"`
+ Issuer string `json:"iss"`
// Audience is who the token was issued for. This is an OAuth2 app usually.
- Audience string `json:"aud"`
+ Audience string `json:"aud"`
// IssuedAt is the token creation time stamp.
- IssuedAt time.Time `json:"iat"`
+ IssuedAt time.Time `json:"iat"`
// ExpiresAt is the expiry timestamp.
- ExpiresAt time.Time `json:"exp"`
+ ExpiresAt time.Time `json:"exp"`
// Extra represents arbitrary session data.
- Extra map[string]interface{} `json:"ext"`
+ Extra map[string]interface{} `json:"ext"`
}
// Firewall offers various validation strategies for access tokens.
type Firewall interface {
- Introspector
-
- // InspectToken checks if the given token is valid and if the requested scopes are satisfied. Returns
+ // TokenValid checks if the given token is valid and if the requested scopes are satisfied. Returns
// a context if the token is valid and an error if not.
//
- // ctx, err := firewall.InspectToken(context.Background(), "access-token", "photos", "files")
+ // ctx, err := firewall.TokenValid(context.Background(), "access-token", "photos", "files")
// fmt.Sprintf("%s", ctx.Subject)
- InspectToken(ctx context.Context, token string, scopes ...string) (*Context, error)
+ TokenValid(ctx context.Context, token string, scopes ...string) (*Context, error)
// IsAllowed uses policies to return nil if the access request can be fulfilled or an error if not.
//
@@ -71,72 +69,8 @@ type Firewall interface {
// TokenFromRequest returns an access token from the HTTP Authorization header.
//
// func anyHttpHandler(w http.ResponseWriter, r *http.Request) {
- // ctx, err := firewall.InspectToken(context.Background(), firewall.TokenFromRequest(r), "photos", "files")
+ // ctx, err := firewall.TokenValid(context.Background(), firewall.TokenFromRequest(r), "photos", "files")
// fmt.Sprintf("%s", ctx.Subject)
// }
TokenFromRequest(r *http.Request) string
}
-
-// Introspection contains an access token's session data as specified by IETF RFC 7662.
-type Introspection struct {
- // Active is a boolean indicator of whether or not the presented token
- // is currently active. The specifics of a token's "active" state
- // will vary depending on the implementation of the authorization
- // server and the information it keeps about its tokens, but a "true"
- // value return for the "active" property will generally indicate
- // that a given token has been issued by this authorization server,
- // has not been revoked by the resource owner, and is within its
- // given time window of validity (e.g., after its issuance time and
- // before its expiration time).
- Active bool `json:"active"`
-
- // Scope is a JSON string containing a space-separated list of
- // scopes associated with this token
- Scope string `json:"scope,omitempty"`
-
- // ClientID is aclient identifier for the OAuth 2.0 client that
- // requested this token.
- ClientID string `json:"client_id,omitempty"`
-
- // Subject of the token, as defined in JWT [RFC7519].
- // Usually a machine-readable identifier of the resource owner who
- // authorized this token.
- Subject string `json:"sub,omitempty"`
-
- // Expires at is an integer timestamp, measured in the number of seconds
- // since January 1 1970 UTC, indicating when this token will expire
- ExpiresAt int64 `json:"exp,omitempty"`
-
- // Issued at is an integer timestamp, measured in the number of seconds
- // since January 1 1970 UTC, indicating when this token was
- // originally issued
- IssuedAt int64 `json:"iat,omitempty"`
-
- // NotBefore is an integer timestamp, measured in the number of seconds
- // since January 1 1970 UTC, indicating when this token is not to be
- // used before
- NotBefore int64 `json:"nbf,omitempty"`
-
- // Username is a human-readable identifier for the resource owner who
- // authorized this token.
- Username int64 `json:"username,omitempty"`
-
- // Audience is a service-specific string identifier or list of string
- // identifiers representing the intended audience for this token
- Audience string `json:"aud,omitempty"`
-
- // Issuer is a string representing the issuer of this token
- Issuer string `json:"iss,omitempty"`
-}
-
-// Introspector is capable of introspecting an access token according to IETF RFC 7662, see:
-// https://tools.ietf.org/html/rfc7662
-type Introspector interface {
- // IntrospectToken performs a token introspection according to IETF RFC 7662, see: https://tools.ietf.org/html/rfc7662
- //
- // func anyHttpHandler(w http.ResponseWriter, r *http.Request) {
- // ctx, err := firewall.InspectToken(context.Background(), firewall.TokenFromRequest(r), "photos", "files")
- // fmt.Sprintf("%s", ctx.Subject)
- // }
- IntrospectToken(ctx context.Context, token string) (*Introspection, error)
-}
diff --git a/oauth2/consent_strategy.go b/oauth2/consent_strategy.go
index 722310599ce..3786a38d1c7 100644
--- a/oauth2/consent_strategy.go
+++ b/oauth2/consent_strategy.go
@@ -71,7 +71,7 @@ func (s *DefaultConsentStrategy) ValidateResponse(a fosite.AuthorizeRequester, t
if ext, ok := t.Claims["id_ext"].(map[string]interface{}); ok {
idExt = ext
}
- if ext, ok := t.Claims["id_ext"].(map[string]interface{}); ok {
+ if ext, ok := t.Claims["at_ext"].(map[string]interface{}); ok {
atExt = ext
}
diff --git a/oauth2/handler.go b/oauth2/handler.go
index be6b020b30e..38500ade435 100644
--- a/oauth2/handler.go
+++ b/oauth2/handler.go
@@ -7,36 +7,77 @@ import (
"github.com/go-errors/errors"
"github.com/julienschmidt/httprouter"
"github.com/ory-am/fosite"
+ "github.com/ory-am/hydra/herodot"
"github.com/ory-am/hydra/pkg"
+ "github.com/ory-am/hydra/firewall"
)
const (
OpenIDConnectKeyName = "hydra.openid.connect"
+
+ ConsentPath = "/oauth2/consent"
+ TokenPath = "/oauth2/token"
+ AuthPath = "/oauth2/auth"
+
+ // IntrospectPath points to the OAuth2 introspection endpoint.
+ IntrospectPath = "/oauth2/introspect"
)
type Handler struct {
- OAuth2 fosite.OAuth2Provider
- Consent ConsentStrategy
- ForcedHTTP bool
+ OAuth2 fosite.OAuth2Provider
+ Consent ConsentStrategy
+
+ Introspector Introspector
+ Firewall firewall.Firewall
+ H herodot.Herodot
+
+ ForcedHTTP bool
+ ConsentURL url.URL
+}
- ConsentURL url.URL
+func (this *Handler) SetRoutes(r *httprouter.Router) {
+ r.POST(TokenPath, this.TokenHandler)
+ r.GET(AuthPath, this.AuthHandler)
+ r.POST(AuthPath, this.AuthHandler)
+ r.GET(ConsentPath, this.DefaultConsentHandler)
+ r.POST(IntrospectPath, this.Introspect)
}
-func (h *Handler) SetRoutes(r *httprouter.Router) {
- r.POST("/oauth2/token", h.TokenHandler)
- r.GET("/oauth2/auth", h.AuthHandler)
- r.POST("/oauth2/auth", h.AuthHandler)
- r.GET("/oauth2/consent", h.DefaultConsentHandler)
+func (this *Handler) Introspect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ var inactive = map[string]bool{"active": false}
+
+ ctx := herodot.NewContext()
+ clientCtx, err := this.Firewall.TokenValid(ctx, this.Firewall.TokenFromRequest(r))
+ if err != nil {
+ this.H.WriteError(ctx, w, r, err)
+ return
+ }
+
+ if err := r.ParseForm(); err != nil {
+ this.H.WriteError(ctx, w, r, err)
+ return
+ }
+
+ auth, err := this.Introspector.IntrospectToken(ctx, r.PostForm.Get("token"))
+ if err != nil {
+ this.H.Write(ctx, w, r, &inactive)
+ return
+ } else if clientCtx.Subject != auth.Audience {
+ this.H.Write(ctx, w, r, &inactive)
+ return
+ }
+
+ this.H.Write(ctx, w, r, auth)
}
-func (o *Handler) TokenHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+func (this *Handler) TokenHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var session = NewSession("")
var ctx = fosite.NewContext()
- accessRequest, err := o.OAuth2.NewAccessRequest(ctx, r, session)
+ accessRequest, err := this.OAuth2.NewAccessRequest(ctx, r, session)
if err != nil {
pkg.LogError(err)
- o.OAuth2.WriteAccessError(w, accessRequest, err)
+ this.OAuth2.WriteAccessError(w, accessRequest, err)
return
}
@@ -49,23 +90,23 @@ func (o *Handler) TokenHandler(w http.ResponseWriter, r *http.Request, _ httprou
}
}
- accessResponse, err := o.OAuth2.NewAccessResponse(ctx, r, accessRequest)
+ accessResponse, err := this.OAuth2.NewAccessResponse(ctx, r, accessRequest)
if err != nil {
pkg.LogError(err)
- o.OAuth2.WriteAccessError(w, accessRequest, err)
+ this.OAuth2.WriteAccessError(w, accessRequest, err)
return
}
- o.OAuth2.WriteAccessResponse(w, accessRequest, accessResponse)
+ this.OAuth2.WriteAccessResponse(w, accessRequest, accessResponse)
}
-func (o *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+func (this *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var ctx = fosite.NewContext()
- authorizeRequest, err := o.OAuth2.NewAuthorizeRequest(ctx, r)
+ authorizeRequest, err := this.OAuth2.NewAuthorizeRequest(ctx, r)
if err != nil {
pkg.LogError(err)
- o.writeAuthorizeError(w, authorizeRequest, err)
+ this.writeAuthorizeError(w, authorizeRequest, err)
return
}
@@ -73,9 +114,9 @@ func (o *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprout
consentToken := authorizeRequest.GetRequestForm().Get("consent")
if consentToken == "" {
// otherwise redirect to log in endpoint
- if err := o.redirectToConsent(w, r, authorizeRequest); err != nil {
+ if err := this.redirectToConsent(w, r, authorizeRequest); err != nil {
pkg.LogError(err)
- o.writeAuthorizeError(w, authorizeRequest, err)
+ this.writeAuthorizeError(w, authorizeRequest, err)
return
}
return
@@ -83,36 +124,36 @@ func (o *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprout
// decode consent_token claims
// verify anti-CSRF (inject state) and anti-replay token (expiry time, good value would be 10 seconds)
- session, err := o.Consent.ValidateResponse(authorizeRequest, consentToken)
+ session, err := this.Consent.ValidateResponse(authorizeRequest, consentToken)
if err != nil {
pkg.LogError(err)
- o.writeAuthorizeError(w, authorizeRequest, errors.New(fosite.ErrAccessDenied))
+ this.writeAuthorizeError(w, authorizeRequest, errors.New(fosite.ErrAccessDenied))
return
}
// done
- response, err := o.OAuth2.NewAuthorizeResponse(ctx, r, authorizeRequest, session)
+ response, err := this.OAuth2.NewAuthorizeResponse(ctx, r, authorizeRequest, session)
if err != nil {
pkg.LogError(err)
- o.writeAuthorizeError(w, authorizeRequest, err)
+ this.writeAuthorizeError(w, authorizeRequest, err)
return
}
- o.OAuth2.WriteAuthorizeResponse(w, authorizeRequest, response)
+ this.OAuth2.WriteAuthorizeResponse(w, authorizeRequest, response)
}
-func (o *Handler) redirectToConsent(w http.ResponseWriter, r *http.Request, authorizeRequest fosite.AuthorizeRequester) error {
+func (this *Handler) redirectToConsent(w http.ResponseWriter, r *http.Request, authorizeRequest fosite.AuthorizeRequester) error {
schema := "https"
- if o.ForcedHTTP {
+ if this.ForcedHTTP {
schema = "http"
}
- challenge, err := o.Consent.IssueChallenge(authorizeRequest, schema+"://"+r.Host+r.URL.String())
+ challenge, err := this.Consent.IssueChallenge(authorizeRequest, schema+"://"+r.Host+r.URL.String())
if err != nil {
return err
}
- p := o.ConsentURL
+ p := this.ConsentURL
q := p.Query()
q.Set("challenge", challenge)
p.RawQuery = q.Encode()
@@ -120,11 +161,11 @@ func (o *Handler) redirectToConsent(w http.ResponseWriter, r *http.Request, auth
return nil
}
-func (o *Handler) writeAuthorizeError(w http.ResponseWriter, ar fosite.AuthorizeRequester, err error) {
+func (this *Handler) writeAuthorizeError(w http.ResponseWriter, ar fosite.AuthorizeRequester, err error) {
if !ar.IsRedirectURIValid() {
var rfcerr = fosite.ErrorToRFC6749Error(err)
- redirectURI := o.ConsentURL
+ redirectURI := this.ConsentURL
query := redirectURI.Query()
query.Add("error", rfcerr.Name)
query.Add("error_description", rfcerr.Description)
@@ -135,5 +176,5 @@ func (o *Handler) writeAuthorizeError(w http.ResponseWriter, ar fosite.Authorize
return
}
- o.OAuth2.WriteAuthorizeError(w, ar, err)
+ this.OAuth2.WriteAuthorizeError(w, ar, err)
}
diff --git a/oauth2/introspector.go b/oauth2/introspector.go
new file mode 100644
index 00000000000..5f11e10e965
--- /dev/null
+++ b/oauth2/introspector.go
@@ -0,0 +1,71 @@
+package oauth2
+
+import "golang.org/x/net/context"
+
+// Introspection contains an access token's session data as specified by IETF RFC 7662, see:
+// https://tools.ietf.org/html/rfc7662
+type Introspection struct {
+ // Active is a boolean indicator of whether or not the presented token
+ // is currently active. The specifics of a token's "active" state
+ // will vary depending on the implementation of the authorization
+ // server and the information it keeps about its tokens, but a "true"
+ // value return for the "active" property will generally indicate
+ // that a given token has been issued by this authorization server,
+ // has not been revoked by the resource owner, and is within its
+ // given time window of validity (e.g., after its issuance time and
+ // before its expiration time).
+ Active bool `json:"active"`
+
+ // Scope is a JSON string containing a space-separated list of
+ // scopes associated with this token.
+ Scope string `json:"scope,omitempty"`
+
+ // ClientID is aclient identifier for the OAuth 2.0 client that
+ // requested this token.
+ ClientID string `json:"client_id,omitempty"`
+
+ // Subject of the token, as defined in JWT [RFC7519].
+ // Usually a machine-readable identifier of the resource owner who
+ // authorized this token.
+ Subject string `json:"sub,omitempty"`
+
+ // Expires at is an integer timestamp, measured in the number of seconds
+ // since January 1 1970 UTC, indicating when this token will expire.
+ ExpiresAt int64 `json:"exp,omitempty"`
+
+ // Issued at is an integer timestamp, measured in the number of seconds
+ // since January 1 1970 UTC, indicating when this token was
+ // originally issued.
+ IssuedAt int64 `json:"iat,omitempty"`
+
+ // NotBefore is an integer timestamp, measured in the number of seconds
+ // since January 1 1970 UTC, indicating when this token is not to be
+ // used before.
+ NotBefore int64 `json:"nbf,omitempty"`
+
+ // Username is a human-readable identifier for the resource owner who
+ // authorized this token.
+ Username int64 `json:"username,omitempty"`
+
+ // Audience is a service-specific string identifier or list of string
+ // identifiers representing the intended audience for this token.
+ Audience string `json:"aud,omitempty"`
+
+ // Issuer is a string representing the issuer of this token
+ Issuer string `json:"iss,omitempty"`
+
+ // Extra is arbitrary data set by the session.
+ Extra map[string]interface{} `json:"ext,omitempty"`
+}
+
+// Introspector is capable of introspecting an access token according to IETF RFC 7662, see:
+// https://tools.ietf.org/html/rfc7662
+type Introspector interface {
+ // IntrospectToken performs a token introspection according to IETF RFC 7662, see: https://tools.ietf.org/html/rfc7662
+ //
+ // func anyHttpHandler(w http.ResponseWriter, r *http.Request) {
+ // ctx, err := introspector.IntrospectToken(context.Background(), introspector.TokenFromRequest(r), "photos", "files")
+ // fmt.Sprintf("%s", ctx.Subject)
+ // }
+ IntrospectToken(ctx context.Context, token string) (*Introspection, error)
+}
diff --git a/oauth2/introspector_http.go b/oauth2/introspector_http.go
new file mode 100644
index 00000000000..8e328150f78
--- /dev/null
+++ b/oauth2/introspector_http.go
@@ -0,0 +1,64 @@
+package oauth2
+
+import (
+ "net/url"
+ "bytes"
+ "io/ioutil"
+ "net/http"
+ "golang.org/x/net/context"
+ "strconv"
+ "encoding/json"
+ "github.com/ory-am/fosite"
+ "golang.org/x/oauth2/clientcredentials"
+ "golang.org/x/oauth2"
+ "github.com/go-errors/errors"
+)
+
+type HTTPIntrospector struct {
+ Client *http.Client
+ Dry bool
+ Endpoint *url.URL
+}
+
+func (this *HTTPIntrospector) TokenFromRequest(r *http.Request) string {
+ return fosite.AccessTokenFromRequest(r)
+}
+
+func (this *HTTPIntrospector) SetClient(c *clientcredentials.Config) {
+ this.Client = c.Client(oauth2.NoContext)
+}
+
+// IntrospectToken is capable of introspecting tokens according to https://tools.ietf.org/html/rfc7662
+//
+// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/oauth2/oauth2-token-introspection
+func (this *HTTPIntrospector) IntrospectToken(ctx context.Context, token string) (*Introspection, error) {
+ var resp = new(Introspection)
+ var ep = *this.Endpoint
+ ep.Path = IntrospectPath
+
+ data := url.Values{"token": []string{token}}
+ hreq, err := http.NewRequest("POST", ep.String(), bytes.NewBufferString(data.Encode()))
+ if err != nil {
+ return nil, errors.New(err)
+ }
+
+ hreq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+ hreq.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
+ hres, err := this.Client.Do(hreq)
+ if err != nil {
+ return nil, errors.New(err)
+ }
+ defer hres.Body.Close()
+
+ if hres.StatusCode < 200 || hres.StatusCode >= 300 {
+ body, _ := ioutil.ReadAll(hres.Body)
+ return nil, errors.Errorf("Expected 2xx status code but got %d.\n%s", hres.StatusCode, body)
+ } else if err := json.NewDecoder(hres.Body).Decode(resp); err != nil {
+ body, _ := ioutil.ReadAll(hres.Body)
+ return nil, errors.Errorf("%s: %s", err, body)
+ } else if !resp.Active {
+ return nil, errors.New("Token is malformed, expired or otherwise invalid")
+ }
+
+ return resp, nil
+}
\ No newline at end of file
diff --git a/oauth2/introspector_local.go b/oauth2/introspector_local.go
new file mode 100644
index 00000000000..8c5ecf2d57e
--- /dev/null
+++ b/oauth2/introspector_local.go
@@ -0,0 +1,45 @@
+package oauth2
+
+import (
+ "github.com/ory-am/fosite"
+ "time"
+ "strings"
+ "net/http"
+ "golang.org/x/net/context"
+ "github.com/Sirupsen/logrus"
+)
+
+type LocalIntrospector struct {
+ OAuth2 fosite.OAuth2Provider
+
+ AccessTokenLifespan time.Duration
+ Issuer string
+}
+
+func (w *LocalIntrospector) TokenFromRequest(r *http.Request) string {
+ return fosite.AccessTokenFromRequest(r)
+}
+
+func (w *LocalIntrospector) IntrospectToken(ctx context.Context, token string) (*Introspection, error) {
+ var session = new(Session)
+ var auth, err = w.OAuth2.ValidateToken(ctx, token, fosite.AccessToken, session)
+ if err != nil {
+ logrus.WithError(err).Infof("Token introspection failed")
+ return &Introspection{
+ Active: false,
+ }, err
+ }
+
+ session = auth.GetSession().(*Session)
+ return &Introspection{
+ Active: true,
+ Subject: session.Subject,
+ Audience: auth.GetClient().GetID(),
+ Scope: strings.Join(auth.GetGrantedScopes(), " "),
+ Issuer: w.Issuer,
+ IssuedAt: auth.GetRequestedAt().Unix(),
+ NotBefore: auth.GetRequestedAt().Unix(),
+ ExpiresAt: session.AccessTokenExpiresAt(auth.GetRequestedAt().Add(w.AccessTokenLifespan)).Unix(),
+ Extra: session.Extra,
+ }, nil
+}
\ No newline at end of file
diff --git a/oauth2/introspector_test.go b/oauth2/introspector_test.go
new file mode 100644
index 00000000000..ca1be2e5dca
--- /dev/null
+++ b/oauth2/introspector_test.go
@@ -0,0 +1,158 @@
+package oauth2_test
+
+import (
+ "testing"
+ "time"
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/net/context"
+ "github.com/ory-am/hydra/pkg"
+ "net/http/httptest"
+ "net/url"
+ "github.com/ory-am/hydra/warden"
+ "github.com/ory-am/fosite"
+ "github.com/julienschmidt/httprouter"
+ "github.com/ory-am/hydra/herodot"
+ foauth2 "github.com/ory-am/fosite/handler/oauth2"
+ goauth2 "golang.org/x/oauth2"
+ "github.com/Sirupsen/logrus"
+ "github.com/ory-am/hydra/oauth2"
+ "github.com/ory-am/ladon"
+)
+
+var (
+ introspectors = make(map[string]oauth2.Introspector)
+ now = time.Now().Round(time.Second)
+ tokens = pkg.Tokens(3)
+ fositeStore = pkg.FositeStore()
+)
+
+var ladonWarden = pkg.LadonWarden(map[string]ladon.Policy{
+ "1": &ladon.DefaultPolicy{
+ ID: "1",
+ Subjects: []string{"alice"},
+ Resources: []string{"matrix", "rn:hydra:token<.*>"},
+ Actions: []string{"create", "decide"},
+ Effect: ladon.AllowAccess,
+ },
+ "2": &ladon.DefaultPolicy{
+ ID: "2",
+ Subjects: []string{"siri"},
+ Resources: []string{"<.*>"},
+ Actions: []string{"decide"},
+ Effect: ladon.AllowAccess,
+ },
+})
+
+var localWarden = &warden.LocalWarden{
+ Warden: ladonWarden,
+ OAuth2: &fosite.Fosite{
+ Store: fositeStore,
+ TokenValidators: fosite.TokenValidators{
+ &foauth2.CoreValidator{
+ CoreStrategy: pkg.HMACStrategy,
+ CoreStorage: fositeStore,
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ },
+ ScopeStrategy: fosite.HierarchicScopeStrategy,
+ },
+ Issuer: "tests",
+ AccessTokenLifespan: time.Hour,
+}
+
+func init() {
+ introspectors["local"] = &oauth2.LocalIntrospector{
+ OAuth2: localWarden.OAuth2,
+ Issuer: "tests",
+ AccessTokenLifespan: time.Hour,
+ }
+
+ r := httprouter.New()
+ serv := &oauth2.Handler{
+ Firewall: localWarden,
+ H: &herodot.JSON{},
+ Introspector: introspectors["local"],
+ }
+ serv.SetRoutes(r)
+ ts = httptest.NewServer(r)
+
+ ar := fosite.NewAccessRequest(oauth2.NewSession("alice"))
+ ar.GrantedScopes = fosite.Arguments{"core"}
+ ar.RequestedAt = now
+ ar.Client = &fosite.DefaultClient{ID: "siri"}
+ ar.Session.(*oauth2.Session).Extra = map[string]interface{}{"foo": "bar"}
+ fositeStore.CreateAccessTokenSession(nil, tokens[0][0], ar)
+
+ ar2 := fosite.NewAccessRequest(oauth2.NewSession("siri"))
+ ar2.GrantedScopes = fosite.Arguments{"core"}
+ ar2.RequestedAt = now
+ ar2.Session.(*oauth2.Session).Extra = map[string]interface{}{"foo": "bar"}
+ ar2.Client = &fosite.DefaultClient{ID: "siri"}
+ fositeStore.CreateAccessTokenSession(nil, tokens[1][0], ar2)
+
+ ar3 := fosite.NewAccessRequest(oauth2.NewSession("siri"))
+ ar3.GrantedScopes = fosite.Arguments{"core"}
+ ar3.RequestedAt = now
+ ar2.Session.(*oauth2.Session).Extra = map[string]interface{}{"foo": "bar"}
+ ar3.Client = &fosite.DefaultClient{ID: "doesnt-exist"}
+ ar3.Session.(*oauth2.Session).AccessTokenExpiry = time.Now().Add(-time.Hour)
+ fositeStore.CreateAccessTokenSession(nil, tokens[2][0], ar3)
+
+ conf := &goauth2.Config{
+ Scopes: []string{},
+ Endpoint: goauth2.Endpoint{},
+ }
+
+ ep, err := url.Parse(ts.URL)
+ if err != nil {
+ logrus.Fatalf("%s", err)
+ }
+ introspectors["http"] = &oauth2.HTTPIntrospector{
+ Endpoint: ep,
+ Client: conf.Client(goauth2.NoContext, &goauth2.Token{
+ AccessToken: tokens[1][1],
+ Expiry: time.Now().Add(time.Hour),
+ TokenType: "bearer",
+ }),
+ }
+}
+
+func TestIntrospect(t *testing.T) {
+ for _, w := range introspectors {
+ for _, c := range []struct {
+ token string
+ expectErr bool
+ assert func(*oauth2.Introspection)
+ }{
+ {
+ token: "invalid",
+ expectErr: true,
+ },
+ {
+ token: tokens[2][1],
+ expectErr: true,
+ },
+ {
+ token: tokens[1][1],
+ expectErr: false,
+ },
+ {
+ token: tokens[0][1],
+ expectErr: false,
+ assert: func(c *oauth2.Introspection) {
+ assert.Equal(t, "alice", c.Subject)
+ assert.Equal(t, "tests", c.Issuer)
+ assert.Equal(t, now.Add(time.Hour).Unix(), c.ExpiresAt, "expires at")
+ assert.Equal(t, now.Unix(), c.IssuedAt, "issued at")
+ assert.Equal(t, map[string]interface{}{"foo": "bar"}, c.Extra)
+ },
+ },
+ } {
+ ctx, err := w.IntrospectToken(context.Background(), c.token)
+ pkg.AssertError(t, c.expectErr, err)
+ if err == nil && c.assert != nil {
+ c.assert(ctx)
+ }
+ }
+ }
+}
diff --git a/policy/doc.go b/policy/doc.go
new file mode 100644
index 00000000000..9712c4c0609
--- /dev/null
+++ b/policy/doc.go
@@ -0,0 +1,15 @@
+// Package policy offers management capabilities for access control policies.
+// To read up on policies, go to:
+//
+// - https://github.com/ory-am/ladon
+//
+// - https://ory-am.gitbooks.io/hydra/content/policy.html
+//
+// Contains source files:
+//
+// - handler.go: A HTTP handler capable of managing policies.
+//
+// - warden_http.go: A Go API using HTTP to validate managing policies.
+//
+// - warden_test.go: Functional tests all of the above.
+package policy
diff --git a/sdk/client.go b/sdk/client.go
index 908fa27460d..9708395a7e3 100644
--- a/sdk/client.go
+++ b/sdk/client.go
@@ -30,19 +30,19 @@ var defaultOptions = []option{
// Client offers easy use of all HTTP clients.
type Client struct {
// Client offers OAuth2 Client management capabilities.
- Client *client.HTTPManager
+ Client *client.HTTPManager
// SSO offers Social Login management capabilities.
- SSO *connection.HTTPManager
+ SSO *connection.HTTPManager
// JWK offers JSON Web Key management capabilities.
- JWK *jwk.HTTPManager
+ JWK *jwk.HTTPManager
// Policies offers Access Policy management capabilities.
Policies *policy.HTTPManager
// Warden offers Access Token and Access Request validation strategies.
- Warden *warden.HTTPWarden
+ Warden *warden.HTTPWarden
http *http.Client
clusterURL *url.URL
@@ -50,10 +50,18 @@ type Client struct {
clientSecret string
skipTLSVerify bool
scopes []string
- credentials clientcredentials.Config
+ credentials clientcredentials.Config
}
// Connect instantiates a new client to communicate with Hydra.
+//
+// import "github.com/ory-am/hydra/sdk"
+//
+// var hydra, err = sdk.Connect(
+// sdk.ClientID("client-id"),
+// sdk.ClientSecret("client-secret"),
+// sdk.ClusterURL("https://localhost:4444"),
+// )
func Connect(opts ...option) (*Client, error) {
c := &Client{}
@@ -125,6 +133,26 @@ func Connect(opts ...option) (*Client, error) {
return c, nil
}
+// OAuth2Config returns an oauth2 config instance which you can use to initiate various oauth2 flows.
+//
+// config := client.OAuth2Config("https://mydomain.com/oauth2_callback", "photos", "contacts.read")
+// redirectRequestTo := oauth2.AuthCodeURL()
+//
+// // in callback handler...
+// token, err := config.Exchange(oauth2.NoContext, authorizeCode)
+func (h *Client) OAuth2Config(redirectURL string, scopes ...string) *oauth2.Config {
+ return &oauth2.Config{
+ ClientSecret: h.clientSecret,
+ ClientID: h.clientID,
+ Endpoint: oauth2.Endpoint{
+ TokenURL: pkg.JoinURL(h.clusterURL, "/oauth2/token").String(),
+ AuthURL: pkg.JoinURL(h.clusterURL, "/oauth2/auth").String(),
+ },
+ Scopes: scopes,
+ RedirectURL: redirectURL,
+ }
+}
+
func (h *Client) authenticate() error {
ctx := context.WithValue(oauth2.NoContext, oauth2.HTTPClient, h.http)
_, err := h.credentials.Token(ctx)
diff --git a/sdk/client_opts.go b/sdk/client_opts.go
index a48572ac0bd..37c0e12fad6 100644
--- a/sdk/client_opts.go
+++ b/sdk/client_opts.go
@@ -7,15 +7,6 @@ import (
"gopkg.in/yaml.v2"
)
-// ClusterURL sets Hydra service URL
-func ClusterURL(urlStr string) option {
- return func(c *Client) error {
- var err error
- c.clusterURL, err = url.Parse(urlStr)
- return err
- }
-}
-
type hydraConfig struct {
ClusterURL string `yaml:"cluster_url"`
ClientID string `yaml:"client_id"`
@@ -50,7 +41,25 @@ func FromYAML(file string) option {
}
}
-// ClientID sets OAuth client ID
+// ClusterURL sets Hydra service URL
+//
+// var hydra, err = sdk.Connect(
+// sdk.ClientID("https://localhost:1234/"),
+// )
+func ClusterURL(urlStr string) option {
+ return func(c *Client) error {
+ var err error
+ c.clusterURL, err = url.Parse(urlStr)
+ return err
+ }
+}
+
+
+// ClientID sets the OAuth2 Client ID.
+//
+// var hydra, err = sdk.Connect(
+// sdk.ClientID("client-id"),
+// )
func ClientID(id string) option {
return func(c *Client) error {
c.clientID = id
@@ -58,7 +67,11 @@ func ClientID(id string) option {
}
}
-// ClientSecret sets OAuth client secret
+// ClientSecret sets OAuth2 Client secret.
+//
+// var hydra, err = sdk.Connect(
+// sdk.ClientSecret("client-secret"),
+// )
func ClientSecret(secret string) option {
return func(c *Client) error {
c.clientSecret = secret
@@ -66,7 +79,11 @@ func ClientSecret(secret string) option {
}
}
-// SkipTLSVerify skips TLS verification
+// SkipTLSVerify skips TLS verification for HTTPS connections.
+//
+// var hydra, err = sdk.Connect(
+// sdk.SkipTLSVerify(),
+// )
func SkipTLSVerify() option {
return func(c *Client) error {
c.skipTLSVerify = true
@@ -74,7 +91,11 @@ func SkipTLSVerify() option {
}
}
-// Scopes sets client scopes granted by Hydra
+// Scopes is a list of scopes that are requested in the client credentials grant.
+//
+// var hydra, err = sdk.Connect(
+// sdk.Scopes("foo", "bar"),
+// )
func Scopes(scopes ...string) option {
return func(c *Client) error {
c.scopes = scopes
diff --git a/sdk/client_opts_test.go b/sdk/client_opts_test.go
index 3d49d2871af..749b735c689 100644
--- a/sdk/client_opts_test.go
+++ b/sdk/client_opts_test.go
@@ -6,7 +6,6 @@ import (
"testing"
"gopkg.in/yaml.v2"
-
"github.com/stretchr/testify/assert"
)
diff --git a/sdk/doc.go b/sdk/doc.go
index a0705ed21b3..2c1fe2eab48 100644
--- a/sdk/doc.go
+++ b/sdk/doc.go
@@ -2,10 +2,11 @@
//
// import "github.com/ory-am/hydra/sdk"
// import "github.com/ory-am/hydra/client"
+//
// var hydra, err = sdk.Connect(
// sdk.ClientID("client-id"),
// sdk.ClientSecret("client-secret"),
-// sdk.ClustURL("https://localhost:4444"),
+// sdk.ClusterURL("https://localhost:4444"),
// )
//
// // You now have access to the various API endpoints of hydra, for example the oauth2 client endpoint:
diff --git a/warden/doc.go b/warden/doc.go
index caf9144758a..0c9f8245c13 100644
--- a/warden/doc.go
+++ b/warden/doc.go
@@ -1,9 +1,18 @@
// Package warden decides if access requests should be allowed or denied. In a scientific taxonomy, the warden
// is classified as a Policy Decision Point. THe warden's primary goal is to implement `github.com/ory-am/hydra/firewall.Firewall`.
+// To read up on the warden, go to:
//
-// This package is structured as follows:
-// * handler.go: A HTTP handler capable of validating access tokens.
-// * warden_http.go: A Go API using HTTP to validate access tokens.
-// * warden_local.go: A Go API using storage managers to validate access tokens.
-// * warden_test.go: Functional tests all of the above.
+// - https://ory-am.gitbooks.io/hydra/content/policy.html
+//
+// - http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers
+//
+// Contains source files:
+//
+// - handler.go: A HTTP handler capable of validating access tokens.
+//
+// - warden_http.go: A Go API using HTTP to validate access tokens.
+//
+// - warden_local.go: A Go API using storage managers to validate access tokens.
+//
+// - warden_test.go: Functional tests all of the above.
package warden
diff --git a/warden/handler.go b/warden/handler.go
index ab3144c713b..199c9eb7c7c 100644
--- a/warden/handler.go
+++ b/warden/handler.go
@@ -14,37 +14,24 @@ import (
)
const (
- TokenValidHandlerPath = "/warden/token/valid"
- TokenAllowedHandlerPath = "/warden/token/allowed"
- AllowedHandlerPath = "/warden/allowed"
- IntrospectPath = "/oauth2/introspect"
-)
-
-type WardenHandler struct {
- H herodot.Herodot
- Warden firewall.Firewall
-}
+ // TokenValidHandlerPath points to the token validation endpoint.
+ TokenValidHandlerPath = "/warden/token/valid"
-func NewHandler(c *config.Config, router *httprouter.Router) *WardenHandler {
- ctx := c.Context()
-
- h := &WardenHandler{
- H: &herodot.JSON{},
- Warden: ctx.Warden,
- }
- h.SetRoutes(router)
+ // TokenAllowedHandlerPath points to the token access request validation endpoint.
+ TokenAllowedHandlerPath = "/warden/token/allowed"
- return h
-}
+ // AllowedHandlerPath points to the access request validation endpoint.
+ AllowedHandlerPath = "/warden/allowed"
+)
-type WardenAuthorizedRequest struct {
+type wardenAuthorizedRequest struct {
Scopes []string `json:"scopes"`
Token string `json:"token"`
}
-type WardenAccessRequest struct {
+type wardenAccessRequest struct {
*ladon.Request
- *WardenAuthorizedRequest
+ *wardenAuthorizedRequest
}
var notAllowed = struct {
@@ -55,40 +42,28 @@ var invalid = struct {
Valid bool `json:"valid"`
}{Valid: false}
-var inactive = struct {
- Active bool `json:"active"`
-}{Active: false}
-
-func (h *WardenHandler) SetRoutes(r *httprouter.Router) {
- r.POST(TokenValidHandlerPath, h.TokenValid)
- r.POST(TokenAllowedHandlerPath, h.TokenAllowed)
- r.POST(AllowedHandlerPath, h.Allowed)
- r.POST(IntrospectPath, h.Introspect)
+// WardenHandler is capable of handling HTTP request and validating access tokens and access requests.
+type WardenHandler struct {
+ H herodot.Herodot
+ Warden firewall.Firewall
}
-func (h *WardenHandler) Introspect(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
- ctx := herodot.NewContext()
- clientCtx, err := h.Warden.InspectToken(ctx, TokenFromRequest(r))
- if err != nil {
- h.H.WriteError(ctx, w, r, err)
- return
- }
+func NewHandler(c *config.Config, router *httprouter.Router) *WardenHandler {
+ ctx := c.Context()
- if err := r.ParseForm(); err != nil {
- h.H.WriteError(ctx, w, r, err)
- return
+ h := &WardenHandler{
+ H: &herodot.JSON{},
+ Warden: ctx.Warden,
}
+ h.SetRoutes(router)
- auth, err := h.Warden.IntrospectToken(ctx, r.PostForm.Get("token"))
- if err != nil {
- h.H.Write(ctx, w, r, &inactive)
- return
- } else if clientCtx.Subject != auth.Audience {
- h.H.Write(ctx, w, r, &inactive)
- return
- }
+ return h
+}
- h.H.Write(ctx, w, r, auth)
+func (h *WardenHandler) SetRoutes(r *httprouter.Router) {
+ r.POST(TokenValidHandlerPath, h.TokenValid)
+ r.POST(TokenAllowedHandlerPath, h.TokenAllowed)
+ r.POST(AllowedHandlerPath, h.Allowed)
}
func (h *WardenHandler) TokenValid(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@@ -102,14 +77,14 @@ func (h *WardenHandler) TokenValid(w http.ResponseWriter, r *http.Request, _ htt
return
}
- var ar WardenAuthorizedRequest
+ var ar wardenAuthorizedRequest
if err := json.NewDecoder(r.Body).Decode(&ar); err != nil {
h.H.WriteError(ctx, w, r, err)
return
}
defer r.Body.Close()
- authContext, err := h.Warden.InspectToken(ctx, ar.Token, ar.Scopes...)
+ authContext, err := h.Warden.TokenValid(ctx, ar.Token, ar.Scopes...)
if err != nil {
h.H.Write(ctx, w, r, &invalid)
return
@@ -163,9 +138,9 @@ func (h *WardenHandler) TokenAllowed(w http.ResponseWriter, r *http.Request, _ h
return
}
- var ar = WardenAccessRequest{
+ var ar = wardenAccessRequest{
Request: new(ladon.Request),
- WardenAuthorizedRequest: new(WardenAuthorizedRequest),
+ wardenAuthorizedRequest: new(wardenAuthorizedRequest),
}
if err := json.NewDecoder(r.Body).Decode(&ar); err != nil {
h.H.WriteError(ctx, w, r, errors.New(err))
diff --git a/warden/warden_http.go b/warden/warden_http.go
index 780f18e20c6..7a29caff35e 100644
--- a/warden/warden_http.go
+++ b/warden/warden_http.go
@@ -4,11 +4,6 @@ import (
"net/http"
"net/url"
- "bytes"
- "encoding/json"
- "io/ioutil"
- "strconv"
-
"github.com/go-errors/errors"
"github.com/ory-am/fosite"
"github.com/ory-am/hydra/firewall"
@@ -33,44 +28,10 @@ func (w *HTTPWarden) SetClient(c *clientcredentials.Config) {
w.Client = c.Client(oauth2.NoContext)
}
-func (w *HTTPWarden) IntrospectToken(ctx context.Context, token string) (*firewall.Introspection, error) {
- var resp = new(firewall.Introspection)
- var ep = *w.Endpoint
- ep.Path = IntrospectPath
- agent := &pkg.SuperAgent{URL: ep.String(), Client: w.Client}
-
- data := url.Values{"token": []string{token}}
- hreq, err := http.NewRequest("POST", ep.String(), bytes.NewBufferString(data.Encode()))
- if err != nil {
- return nil, errors.New(err)
- }
-
- hreq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- hreq.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
- hres, err := w.Client.Do(hreq)
- if err != nil {
- return nil, errors.New(err)
- }
-
- if hres.StatusCode < 200 || hres.StatusCode >= 300 {
- body, _ := ioutil.ReadAll(hres.Body)
- return nil, errors.Errorf("Expected 2xx status code but got %d.\n%s", hres.StatusCode, body)
- } else if err := json.NewDecoder(hres.Body).Decode(resp); err != nil {
- body, _ := ioutil.ReadAll(hres.Body)
- return nil, errors.Errorf("%s: %s", err, body)
- }
-
- if err := agent.POST(&struct {
- Token string `json:"token"`
- }{Token: token}, &hres); err != nil {
- return nil, err
- } else if !resp.Active {
- return nil, errors.New("Token is malformed, expired or otherwise invalid")
- }
-
- return resp, nil
-}
-
+// TokenAllowed checks if a token is valid and if the token owner is allowed to perform an action on a resource.
+// This endpoint requires a token, a scope, a resource name, an action name and a context.
+//
+// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers/check-if-an-access-tokens-subject-is-allowed-to-do-something
func (w *HTTPWarden) TokenAllowed(ctx context.Context, token string, a *ladon.Request, scopes ...string) (*firewall.Context, error) {
var resp = struct {
*firewall.Context
@@ -80,8 +41,8 @@ func (w *HTTPWarden) TokenAllowed(ctx context.Context, token string, a *ladon.Re
var ep = *w.Endpoint
ep.Path = TokenAllowedHandlerPath
agent := &pkg.SuperAgent{URL: ep.String(), Client: w.Client}
- if err := agent.POST(&WardenAccessRequest{
- WardenAuthorizedRequest: &WardenAuthorizedRequest{
+ if err := agent.POST(&wardenAccessRequest{
+ wardenAuthorizedRequest: &wardenAuthorizedRequest{
Token: token,
Scopes: scopes,
},
@@ -95,6 +56,9 @@ func (w *HTTPWarden) TokenAllowed(ctx context.Context, token string, a *ladon.Re
return resp.Context, nil
}
+// IsAllowed checks if an arbitrary subject is allowed to perform an action on a resource.
+//
+// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers/check-if-a-subject-is-allowed-to-do-something
func (w *HTTPWarden) IsAllowed(ctx context.Context, a *ladon.Request) error {
var allowed = struct {
Allowed bool `json:"allowed"`
@@ -112,7 +76,10 @@ func (w *HTTPWarden) IsAllowed(ctx context.Context, a *ladon.Request) error {
return nil
}
-func (w *HTTPWarden) InspectToken(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) {
+// TokenValid checks if an access token is valid. You must provide a token and a scope.
+//
+// The HTTP API is documented at http://docs.hdyra.apiary.io/#reference/warden:-access-control-for-resource-providers/check-if-an-access-token-is-valid
+func (w *HTTPWarden) TokenValid(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) {
var resp = struct {
*firewall.Context
Valid bool `json:"valid"`
@@ -121,7 +88,7 @@ func (w *HTTPWarden) InspectToken(ctx context.Context, token string, scopes ...s
var ep = *w.Endpoint
ep.Path = TokenValidHandlerPath
agent := &pkg.SuperAgent{URL: ep.String(), Client: w.Client}
- if err := agent.POST(&WardenAuthorizedRequest{
+ if err := agent.POST(&wardenAuthorizedRequest{
Token: token,
Scopes: scopes,
}, &resp); err != nil {
diff --git a/warden/warden_local.go b/warden/warden_local.go
index b0c1bc28639..cfde59b03de 100644
--- a/warden/warden_local.go
+++ b/warden/warden_local.go
@@ -5,8 +5,6 @@ import (
"time"
- "strings"
-
"github.com/Sirupsen/logrus"
"github.com/go-errors/errors"
"github.com/ory-am/fosite"
@@ -17,8 +15,8 @@ import (
)
type LocalWarden struct {
- Warden ladon.Warden
- OAuth2 fosite.OAuth2Provider
+ Warden ladon.Warden
+ OAuth2 fosite.OAuth2Provider
AccessTokenLifespan time.Duration
Issuer string
@@ -28,29 +26,6 @@ func (w *LocalWarden) TokenFromRequest(r *http.Request) string {
return fosite.AccessTokenFromRequest(r)
}
-func (w *LocalWarden) IntrospectToken(ctx context.Context, token string) (*firewall.Introspection, error) {
- var session = new(oauth2.Session)
- var auth, err = w.OAuth2.ValidateToken(ctx, token, fosite.AccessToken, session)
- if err != nil {
- logrus.WithError(err).Infof("Token introspection failed")
- return &firewall.Introspection{
- Active: false,
- }, err
- }
-
- session = auth.GetSession().(*oauth2.Session)
- return &firewall.Introspection{
- Active: true,
- Subject: session.Subject,
- Audience: auth.GetClient().GetID(),
- Scope: strings.Join(auth.GetGrantedScopes(), " "),
- Issuer: w.Issuer,
- IssuedAt: auth.GetRequestedAt().Unix(),
- NotBefore: auth.GetRequestedAt().Unix(),
- ExpiresAt: session.AccessTokenExpiresAt(auth.GetRequestedAt().Add(w.AccessTokenLifespan)).Unix(),
- }, nil
-}
-
func (w *LocalWarden) IsAllowed(ctx context.Context, a *ladon.Request) error {
if err := w.Warden.IsAllowed(a); err != nil {
logrus.WithFields(logrus.Fields{
@@ -79,7 +54,7 @@ func (w *LocalWarden) TokenAllowed(ctx context.Context, token string, a *ladon.R
return w.allowed(ctx, a, scopes, auth, session)
}
-func (w *LocalWarden) InspectToken(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) {
+func (w *LocalWarden) TokenValid(ctx context.Context, token string, scopes ...string) (*firewall.Context, error) {
var session = new(oauth2.Session)
var oauthRequest = fosite.NewAccessRequest(session)
diff --git a/warden/warden_test.go b/warden/warden_test.go
index 60ef22957ae..3df88a0fc29 100644
--- a/warden/warden_test.go
+++ b/warden/warden_test.go
@@ -211,41 +211,6 @@ func TestActionAllowed(t *testing.T) {
}
}
-func TestIntrospect(t *testing.T) {
- for n, w := range wardens {
- for k, c := range []struct {
- token string
- expectErr bool
- assert func(*firewall.Introspection)
- }{
- {
- token: "invalid",
- expectErr: true,
- },
- {
- token: tokens[2][1],
- expectErr: true,
- },
- {
- token: tokens[0][1],
- expectErr: false,
- assert: func(c *firewall.Introspection) {
- assert.Equal(t, "alice", c.Subject)
- assert.Equal(t, "tests", c.Issuer)
- assert.Equal(t, now.Add(time.Hour).Unix(), c.ExpiresAt)
- assert.Equal(t, now.Unix(), c.IssuedAt)
- },
- },
- } {
- ctx, err := w.IntrospectToken(context.Background(), c.token)
- pkg.AssertError(t, c.expectErr, err, "TestIntrospect case", n, k)
- if err == nil && c.assert != nil {
- c.assert(ctx)
- }
- }
- }
-}
-
func TestAllowed(t *testing.T) {
for n, w := range wardens {
for k, c := range []struct {
@@ -333,7 +298,7 @@ func TestTokenValid(t *testing.T) {
expectErr: true,
},
} {
- ctx, err := w.InspectToken(context.Background(), c.token, c.scopes...)
+ ctx, err := w.TokenValid(context.Background(), c.token, c.scopes...)
pkg.AssertError(t, c.expectErr, err, "ActionAllowed case", n, k)
if err == nil && c.assert != nil {
c.assert(ctx)