Coder Social home page Coder Social logo

geheimtur's Introduction

Geheimtür Build Status cljdoc

a Clojure library that allows you to secure your Pedestal applications with minimum efforts.

Live Demo

Motivation

I do know that there is a great friend library out there, but I had some problems making it work with a Pedestal application and do that the way I wanted, so I decided to implement something that does (hopefully) some good work securing Pedestal applications as easily (hopefully) as friend does with Ring applications.

Also, I didn't want to mess around with routing that is handled quite nicely by Pedestal itself, so if an authentication flow requires some extra routes to be added, those route should be plugged into the Pedestal routing system manually.

ChangeLog

The ChangeLog and migration instructions can be found in CHANGES.md.

Usage

Include the library in your leiningen project dependencies:

[geheimtur "0.4.0"]

Examples

You can find the sources of a demo application in geheimtur-demo repository.

The examples below do not duplicate information available as docstrings, if you want to know all available options - check the docs in the code.

Securing a page

When you need to limit access to a specific page or a sub-tree of pages, you just add the guard interceptor to the desired location. You can adjust the interceptor behaviour using the following optional parameters:

  • :roles - a set of roles that are allowed to access the page, if not defined users are required to just be authenticated
  • :silent? - a flag to affect unauthorized/unauthenticated behaviours. If set to true (default), users will be getting a 404 Not Found error page when they don't have access rights
  • :unauthenticated-fn - an unauthenticated error state handler. It's a function that accepts a Pedestal context. The default implementation, throws an exception with a type of :unauthenticated
  • :unauthorized-fn - an unauthorized error state handler. It's a function that accepts a Pedestal context. The default implementation, throws an exception with a type of :unauthorized

In the example below, only administrators are allowed to access the pages under the /admin path, the rest will be getting a 404 responses (Note: this is an illustration of guard usage and not a completely functional example.)

(def routes
  #{["/" :get `views/home-page]
    ["/admin" :get [(guard :roles #{:admin}) `views/admin]]})

Enabling a flow

When an unauthenticated user or a user with missing access rights reaches a page secured with guard, the guard will throw an exception that can be handled either by the http-basic or interactive interceptor that determines which flow is going to be triggered.

Http-Basic

You can enable http-basic authentication by putting the http-basic interceptor before any of your guards. It takes the following parameters:

  • realm - a string that will be shown to a user when s/he is prompted to enter credentials
  • credential-fn - a function that, given a request context and a map with username and password, returns a corresponding identity
(def routes
  #{["/"      :get [(http-basic "Secure App" get-identity-from-db) `views/home-page]]
    ["/admin" :get [(http-basic "Secure App" get-identity-from-db) (guard :roles #{:admin}) `views/admin]]})

Form-based

You can use the interactive interceptor to redirect users to the login page when they are requested to be authenticated by a guard. At this moment, it accepts only one configuration option - :login-uri, by default users are redirected to the /login page.

(def routes
  #{["/"      :get [(interactive {:login-uri "/users/login"}) `views/home-page]]
    ["/admin" :get [(interactive {:login-uri "/users/login"}) (guard :roles #{:admin}) `views/admin]])

After doing so, you just need to add handlers that render the login page and authenticate users. Geheimtur comes with a default :POST handler that can be used to authenticate users when you don't want to implement your own. The form-based interceptor requires sessions to be enabled.

(def common-interceptors [(body-params/body-params) bootstrap/html-body session-interceptor])
(def interactive-interceptors (into common-interceptors [access-forbidden-interceptor (interactive {})]))

(def routes
  #{["/"                       :get (conj common-interceptors `views/home-page)]
    ["/login"                  :get (conj common-interceptors `view/login-page)]
    ["/login"                  :post (conj common-interceptors (default-login-handler {:credential-fn credentials
                                                                                       :form-reader   identity}))]
    ["/logout"                 :get (conj common-interceptors default-logout-handler)]
    ["/interactive"            :get (conj interactive-interceptors `views/interactive-index)]
    ["/interactive/restricted" :get (into interactive-interceptors [(guard :silent? false) `views/interactive-restricted])]})

A complete example can be found here.

OAuth 2.0

You can use the same interactive inteceptor to redirect users to a page where they choose supported identity providers. Geheimtur provides handlers for users redirection and callbacks out of the box, all you need to do is to configure providers available for your users.

Please see authenticate-handler documentation for the description of all possible provider options.

(def providers
  {:github {:auth-url           "https://github.com/login/oauth/authorize"
            :client-id          (or (System/getenv "github.client_id") "client-id")
            :client-secret      (or (System/getenv "github.client_secret") "client-secret")
            :scope              "user:email"
            :token-url          "https://github.com/login/oauth/access_token"
            :user-info-url      "https://api.github.com/user"
            :user-info-parse-fn #(-> % :body (parse-string true))}})

(def common-interceptors [(body-params/body-params) bootstrap/html-body session-interceptor])
(def interactive-interceptors (into common-interceptors [access-forbidden-interceptor (interactive {})]))

(def routes
  #{["/"                       :get (conj common-interceptors `views/home-page)]
    ["/login"                  :get (conj common-interceptors `view/login-page)]
    ["/login"                  :post (conj common-interceptors (default-login-handler {:credential-fn credentials
                                                                                       :form-reader   identity}))]
    ["/oauth.login"            :get (conj common-interceptors (authenticate-handler providers))]
    ["/oauth.callback"         :get (conj common-interceptors (callback-handler providers))]
    ["/logout"                 :get (conj common-interceptors default-logout-handler)]
    ["/interactive"            :get (conj interactive-interceptors `views/interactive-index)]
    ["/interactive/restricted" :get (into interactive-interceptors [(guard :silent? false) `views/interactive-restricted])]})

A complete example can be found here.

Token-based

If you would like to secure your API using bearer tokens (or any other kind of tokens), token interceptor is something you might want to consider.

Please see token interceptor documentation for the description of all possible options.

(def routes
  #{["/api/restricted" :get  [http/json-body (token token-credentials) (guard :silent? false) `views/api-restricted]]})

A complete example can be found here.

License

Copyright © 2015-2018 Pavel Prokopenko

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

geheimtur's People

Contributors

anthgur avatar ddeaguiar avatar deathtenk avatar milt avatar otann avatar propan avatar stuarth avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

geheimtur's Issues

Guard interceptor blocks OPTIONS requests

If the client is making CORS requests to a pedestal server, the preflight OPTIONS request will not include credentials, so the guard interceptor will cause it to be refused.

Extra query params per provider?

First off, thanks for your great work on this!

We've run into a problem while implementing a Google provider, namely that Geheimtur doesn't allow the addition of extra query params to initial auth requests in geheimtur.impl.oauth2/authenticate-handler. This is a bit of a dealbreaker with providers like Google that (annoyingly) use extra params to control requested access. For instance, the access_type param controls whether or not a refresh token will be available (see "Step 2").

This could be alleviated pretty easily by adding a key to the provider map with a map value which would be converted to additional query params.

basic auth wrong return code

when a user accesses a resource restricted via basic auth, the return code should be 401 and not 403, so that the browser asks for the credentials again.

Not compatible with pedestal 0.4.0

Exception in thread "main" java.lang.ExceptionInInitializerError
    at clojure.main.<clinit>(main.java:20)
Caused by: java.lang.IllegalAccessError: definterceptorfn does not exist, compiling:(geheimtur/interceptor.clj:1:1)

Consider adding a CHANGES.md or similar

  1. Love this project. :)
  2. It would be super helpful to understand what changes (especially breaking changes) were introduced from version to version without needing to view the Git log / read all of the code to find out.

For instance, I am still running version 0.2.1 and would like to be on the latest stable release, but have to do some log surfing to figure out if/how the changes might affect my application. Again, thank you for releasing this project. Cheers!

Client secret rotation

Hi!

While using geheimtur, I encountered a problem, where my app cannot login new users after a day of uptime. I've discovered that in our company keys are rotated daily, and my app was using outdated one.

I see in the code of geheimtur.impl.oauth2/authenticate-handler handler is created and is bound to the value it reads from providers map.

Could you please consider adding an option to give providers-fn that will provide value in runtime or some similar functionality?

And of course, thank you for the excellent library.

Live Demo Broken

Application error

An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details. You can do this from the Heroku CLI with the command
heroku logs --tail

'fetch-token' assumes that the response is form-urlencoded but that's not necessarily the case

In the fetch-token function, ring-codec/form-decode is used to parse the response. While this works with Github, I had issues setting up integration with Google whose token response payload is json.

There are two ways that this can be handled:

  1. Use different token response parse fn's based on the token response Content-Type
  2. Allow the user to provide a token-response-parse-fn in the provider config

I'd prefer the former over the later. Pedestal already includes Cheshire so we can use it to parse json responses.

What are your thoughts? I'd be happy to work on a pull request.

Logo proposal

I have a logo proposal for you. I want to be a contributor. What do you say?

credentials extra arity with request

I was thinking that sometimes it would be usful to have access to the request information when writing a credentials function.

For example I was playing around with the idea of attaching a datomic db instance into the request. Then I would want to do the user lookup based on that db instance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.