Giter Club home page Giter Club logo

oauth2's Introduction

OAuth2 (v0.7.0) BuildStatus

This library is designed to simplify the implementation of the server side of OAuth2 (http://tools.ietf.org/html/rfc6749). It provides no support for developing clients. See oauth2_client for support in accessing Oauth2 enabled services.

oauth2 is released under the terms of the MIT license

Current stable version: 0.6.1

Current α alpha version: 0.7.x

copyright 2012-2015 Kivra

tl;dr

Examples

Check out the examples.

Related projects

Webmachine server implementation by Oauth2 contributor Ivan Martinez: oauth2_webmachine.

Redis backed Oauth2 backend.

Concepts

Tokens

A token is a (randomly generated) string provided to the client by the server in response to some form of authorization request. There are several types of tokens:

  • Access Token: An access token identifies the origin of a request for a privileged resource.
  • Refresh Token: A refresh token can be used to replace an expired access token.

Expiry

Access tokens can (optionally) be set to expire after a certain amount of time. An expired token cannot be used to gain access to resources.

Identities

A token is associated with an identity -- a value that uniquely identifies a user, client or agent within your system. Typically, this is a user identifier.

Scope

The scope is handled by the backend implementation. The specification outlines that the scope is a space delimetered set of parameters. This library has been developed with the following in mind.

Scope is implemented as a set and loosely modeled after the Solaris RBAC priviliges, i.e. solaris.x.* and implemented as a MAC with the ability to narrow the scope but not extend it beyond the predefined scope.

But since the scope is opaque to this Oauth2 implementation you can use the scoping strategy that best suit your workflow.

There is a utility module to work with scope. The recommendation is to pass a Scope as a list of binaries, i.e. [<<"root.a.c.b">>, <<"root.x.y.z">>] you can then validate these against another set like:

> oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]),
                            oauth2_priv_set:new([<<"root.*">>])).
true
> oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]),
                            oauth2_priv_set:new([<<"root.x.y">>])).
false
> oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a.b">>, <<"root.x.y">>]),
                            oauth2_priv_set:new([<<"root.a.*">>, <<"root.x.y">>])).
true

Clients

If you have many diverse clients connecting to your service -- for instance, a web client and an iPhone app -- it's desirable to be able to distinguish them from one another and to be able to grant or revoke privileges based on the type the client issuing a request. As described in the OAuth2 specification, clients come in two flavors:

  • Confidential clients, which can be expected to keep their credentials from being disclosed. For instance, a web site owned and operated by you could be regarded as confidential.
  • Public clients, whose credentials are assumed to be compromised the moment the client software is released to the public.

Clients are distinguished by their identifiers, and can (optionally) be authenticated using a secret key shared between the client and server.

Testing

If you want to run the EUnit test cases, you can do so with:

$ make ct

Customization

The library makes no assumptions as to how you want to implement authentication and persistence of users, clients and tokens. Instead, it provides a behavior (oauth2_backend) with functions that needs to be implemented. To direct calls to a different backend module, simply set {backend, your_backend_module} in the oauth2 section of your app.config.

Look at oauth2_mock_backend for how a backend can be implemented.

The following example demonstrates a basic app.config section for oauth2.

[
    {oauth2, [
        %% Default expiry_time for access_tokens unless
        %% overridden per flow
        {expiry_time, 3600}
        ,{backend, backend_goes_here}

        %% Optional expiry_time override per flow
        ,{password_credentials, [
            {expiry_time, 7200}
        ]}
        ,{client_credentials, [
            {expiry_time, 86400}
        ]}
        ,{refresh_token, [
            {expiry_time, 2592000} %% 30 Days
        ]}
        ,{code_grant, [
            %% Recommended absolute expiry time from the spec
            {expiry_time, 600}
        ]}
    ]}
].

A complete list of functions that your backend must provide is available by looking at oauth2_backend.erl, which contains documentation and function specifications.

To implement a custom token generation backend you can change your app.config as such:

[
    {oauth2, [
        {token_generation, YOUR_TOKEN_GENERATOR}
    ]}
].

The default token generator is called oauth2_token. To implement your own you should create your own module implementing the oauth2_token_generation behavior exporting one function generate/0.

oauth2's People

Contributors

bipthelin avatar danielwhite avatar drobakowski avatar kampiliira avatar kivradawi avatar licenser avatar mdaguete avatar mtornwall avatar onno-vos-kivra avatar plux avatar plux-kivra 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 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

oauth2's Issues

Remove resource owner authentication

This issue is a consecuence of the discussion in #47. @danielwhite pointed out the fact that resource owner authentication is out of the scope of the specification, and could be other than a username and a password. As a consecuence, I think it should be out of the scope of the library. All API functions that currently receive username and password as parameters, should receive an already authenticated resource owner term() instead, as oauth2:authorize_resource_owner/3 already does. It's the application who should take care of authenticating the resource owner in the desired way.
What is @bipthelin 's opinion about this?.

Release request

Hello!

I am working on packaging oauth2 for Fedora Rawhide, and the latest release (0.6.0) won't build due to #59. Would you be willing to make a release with that fix so we can build there? Thanks so much for your consideration!

type map(_,_) undefined with Erlang 18

Someone have any idea what is the problem? :)

myproject/deps/oauth2/src/oauth2_response.erl:174: type map(_,_) undefined

If I modify the code as:

-spec to_map(response()) -> map().

then, I can compile the code with rebar.

verify_redirection_uri improvements

  • The specification allows to register more than one redirection URI for a client (see point 3.1.2.3), but the current implementation of oauth2:verify_redirection_uri/2 expects just one. This function should be left unimplemented in oauth2_backend. (The specification also mentions the case where "part of the redirection URI has been registered", but it's not clear about this).
  • verify_redirection_uri should take a client identity instead of a client ID. This function is always called after obtaining the client identity with oauth2_backend:get_client_identity/2 or oauth2_backend:authenticate_client/3. If these functions obtain the registered redirection URI and add it to the identity, verify_redirection_uri doesn't need to look in the database again.

Can't see how and why oauth2_backend:verify_resowner_scope/3 returns a client

Hello all,
I can't understand the change made here to verify_resowner_scope's result:
511fc9c
How can the function return a client from a resource owner and a scope?. And what is the point of doing so?. Apparently it has to be the same client as the one returned by get_client_identity/2 in oauth2:authorize_code_request/6. How is this supposed to work?.
Thank you,
Ivan

authorize_password/7

You have this authorization method:

%% @doc Validates a request for an access token from client and resource
%%      owner's credentials. Use it to implement the following steps of
%%      RFC 6749:
%%      - 4.2.1. Implicit Grant > Authorization Request. when the client
%%      is public.
-spec authorize_password( binary(), binary(), binary(), binary(), binary()
                        , scope(), appctx())
                            -> {ok, {appctx(), auth()}} | {error, error()}.
authorize_password(CId, CSecret, RedirUri, UId, Pwd, Scope, AppCtx1) ->
    case ?BACKEND:authenticate_client(CId, CSecret, AppCtx1) of
        {error, _}              -> {error, invalid_client};
        {ok, {AppCtx2, Client}} ->
            case ?BACKEND:verify_redirection_uri(Client, RedirUri, AppCtx2) of
                {ok, AppCtx3} ->
                    case authorize_password(UId, Pwd, Scope, AppCtx3) of
                        {error, _} = E        -> E;
                        {ok, {AppCtx4, Auth}} -> {ok, {AppCtx4, Auth#a{client=Client}}}
                    end;
                _ -> {error, invalid_grant}
            end
    end.

But RFC says that we should accept only response_type, client_id and redirect_uri, where redirect_uri must be blank.html that serves on oauth server.

Scope special case for privilege sets

Hi @bipthelin,

thanks for your great work on the module oauth_priv_set. I’m not very familiar with Solaris RBAC privileges and so I'm a little bit confused while using the module. For example the second set parameter of

1> oauth2_priv_set:is_subset(oauth2_priv_set:new([<<"root.a">>]), oauth2_priv_set:new([<<"root.a.b">>])).
true

should be used as the predefined scope right? In the sense of a privilege set where the granted privileges are getting more restrictive with the trees depth, shouldn't this example result in false? Same thing for:

2> oauth2_priv_set:is_member(<<"root.a">>, oauth2_priv_set:new([<<"root.a.b">>])).
true

Maybe I'm just not getting the point.

warnings_as_errors makes tests compilation fail

warnings_as_errors is enabled in "rebar.tests.config". This produces the following error:
[user@localhost oauth2]$ rebar clean
==> oauth2 (clean)
[user@localhost oauth2]$ rebar -C rebar.tests.config compile
==> Entering directory `/home/.../oauth2/deps/meck'
...
Compiled src/oauth2_token.erl
compile: warnings being treated as errors
src/oauth2_backend.erl:179: function get_client_identity/2 is unused

Neither oauth2:issue_code_grant/3 nor /4 verify the scope

Neither issue_code_grant/3 nor the /4 version verify the scope. Accordingly to points 4.1.1 and 4.1.3 of the specification, a scope parameter is passed in the authorization request, and a invalid_scope error could be returned. These functions should verify the scope the same way authorize_client_credentials/3 and authorize_password/3 do.

oauth2_config: what's the point?

I wonder if configuration proplist can be passed to each function so that one could use several oauth2 configurations (can't yet put clear use case beyond dynamic config changes) and un-pin library off application:get_env/2 which is way inflexible in cases other than boring ( :) ) vanilla releases?
--Vladimir

proper errors in tests

$ rebar -C rebar.tests.config eunit skip-deps=true
...
    proper_tests:818: false_props_test_...*failed*
in function proper:threw_exception/2 (src/proper.erl, line 1361)
  called as threw_exception(#Fun<proper_tests.767.57308795>,[{lists,min,[[]],[{file,"lists.erl"},{line,249}]},
 {proper_tests,'-false_props_test_/0-fun-69-',1,
               [{file,"test/proper_tests.erl"},{line,818}]},
 {proper,apply_args,3,[{file,"src/proper.erl"},{line,1309}]},
 {proper,perform,7,[{file,"src/proper.erl"},{line,1102}]},
 {proper,inner_test,2,[{file,"src/proper.erl"},{line,985}]},
 {proper,test,2,[{file,"src/proper.erl"},{line,977}]},
 {proper_tests,'-false_props_test_/0-fun-84-',0,[{file,[...]},{line,...}]},
 {eunit_test,run_testfun,1,[{file,...},{...}]}])
in call from proper:apply_args/3 (src/proper.erl, line 1316)
in call from proper:perform/7 (src/proper.erl, line 1102)
in call from proper:inner_test/2 (src/proper.erl, line 985)
in call from proper:test/2 (src/proper.erl, line 977)
in call from proper_tests:'-false_props_test_/0-fun-84-'/0 (test/proper_tests.erl, line 818)
**error:function_clause


    proper_tests:819: false_props_test_...[0.003 s] ok
...
    proper_tests:890: error_props_test_...*failed*
in function proper:threw_exception/2 (src/proper.erl, line 1361)
  called as threw_exception(#Fun<proper_tests.404.57308795>,[{proper_tests,'-error_props_test_/0-fun-18-',
               [[2,0]],
               [{file,"test/proper_tests.erl"},{line,891}]},
 {proper,apply_args,3,[{file,"src/proper.erl"},{line,1309}]},
 {proper,perform,7,[{file,"src/proper.erl"},{line,1102}]},
 {proper,inner_test,2,[{file,"src/proper.erl"},{line,985}]},
 {proper,test,2,[{file,"src/proper.erl"},{line,977}]},
 {proper_tests,'-error_props_test_/0-fun-19-',0,
               [{file,"test/proper_tests.erl"},{line,890}]},
 {proper_tests,'-error_props_test_/0-fun-24-',0,[{file,[...]},{line,...}]},
 {eunit_test,run_testfun,1,[{file,...},{...}]}])
in call from proper:apply_args/3 (src/proper.erl, line 1316)
in call from proper:perform/7 (src/proper.erl, line 1102)
in call from proper:inner_test/2 (src/proper.erl, line 985)
in call from proper:test/2 (src/proper.erl, line 977)
in call from proper_tests:'-error_props_test_/0-fun-19-'/0 (test/proper_tests.erl, line 890)
in call from proper_tests:'-error_props_test_/0-fun-24-'/0 (test/proper_tests.erl, line 890)
**error:function_clause


    proper_tests:892: error_props_test_...[0.001 s] ok
...
=======================================================
  Failed: 2.  Skipped: 0.  Passed: 426.
ERROR: One or more eunit tests failed.

The library can't report invalid_scope

oauth2:authorize_client_credentials/3 function performs client credentials and scope verification. Since the function either succeeds or returns {error, invalid_client}, there is no way of returning an invalid_scope error to the caller.
The same goes for every oauth2 function that verifies the scope: authorize_password/2, issue_code_grant/3, issue_code_grant/4, issue_code_grant/5 and authorize_client_credentials/3.

token revocation by a user

Hi guys,
somebody know if oauth2 supports token revocation by the user who gave the access?
This is the use case:

  1. The user gives access to X to the client -> authorization token is generated.
  2. The client transform the auth token in an access token.
  3. The user remove the access token.

clarification needed

Please, clarify what is ResOwner in https://github.com/kivra/oauth2/blob/master/src/oauth2.erl#L114, how one obtains it from web request, and why issue_code_grant in fact returns 'response' which must further be processed to obtain namely the code?

In the absense of authorization grant flow in oauth2_example it's hard to catch the logic.

TIA,
--Vladimir

PS:

Am finding ways to get rid of my custom DIY backend at https://github.com/dvv/saddle/blob/master/src/oauth2_provider.erl in favor of oauth2... Wonder if you could assist and then the code can be used as modern example.

--Vladimir

Can’t refresh access tokens generated with Resource Owner Password Credentials Grant and oauth2:authorize_password/4

The function oauth2:refresh_access_token/2 throws an {badmatch,{ok,undefined}} exception while trying to get the client from the grant context: 284> {ok, Client} = get(GrantCtx, <<"client">>). The problem results from using oauth2:authorize_password/4 for a Resource Owner Password Credentials Grant, where no client gets associated while generating the #authorization{} response in oauth2:authorize_resource_owner/3. I’m not quite sure what’s the best way to fix this without breaking the existing API. In conjunction with RFC6749 / Section 4.3.2:

...
The authorization server MUST:

   o  require client authentication for confidential clients or for any
      client that was issued client credentials (or with other
      authentication requirements),

   o  authenticate the client if client authentication is included, and

   o  …

I would suggest to define an additional function oauth2:authorize_password(CId, CSecret, UId, Pwd, Scope, AppCtx1) to authenticate the client and user and:

(1)

  • Add the client as an additional parameter to oauth2:authorize_resource_owner(Client, ResOwner, Scope, AppCtx1)
  • Let oauth2_backend:authenticate_username_password/3 return {ok, {AppCtx2, Client, ResOwner}} instead of {ok, {AppCtx2, ResOwner}} so that oauth2:authorize_password/4 could also pass it to the modified oauth2:authorize_resource_owner/4

(2)

  • Let oauth2_backend:verify_resowner_scope/3 return {ok, {AppCtx2, Client, Scope2}} instead of {ok, {AppCtx2, Scope2}} so that oauth2:authorize_resource_owner/3 could set the client for the #authorization{} response itself.

I tend to (2), it seems less intrusive? Other suggestions?

Refresh token

Hello,

How can I get an optional refresh token when using '4.3.2. Access Token Request' with a public client ?
It isn't clear for me that the rfc avoid it ..

Thank you. Regards.

context as part of access token

Hi!

I would like to drive a store-less auth system -- that is i'd like to encode context in signed encrypted strings. Similar to how they use cookies to store data.
However, the current code infractructure strongly separates access token and context:

associate_access_token(AccessToken, Context) -> ok.
resolve_access_token(AccessToken) -> {ok, context()}.

resolve_access_token/1 is simple to accomodate to my flow, but associate_access_token/2 is not as it just returns fixed atom.
I would have been ok if the backend would be given chance to generate access token from context, say:

associate_access_token(AccessToken, Context) -> ok | {ok, NewAccessToken}.

What do you think: is it ever feasible? Maybe another ways of doing what I need which I don't see?

TIA,
--Vladimir

expires_in parameter is converted to a binary by oauth2_response:to_proplist/1

The OAuth 2.0 specification seems to indicate that the expires_in parameter is encoded in the form:

{
  "expires_in":3600
}

oauth2_response:to_proplist/1 seems to be intended as a convenience function for passing the response's contents into a JSON encoder. By converting this value to a binary, it becomes more difficult to encode without performing an extra translation step.

Looking at the history, this seems intended, so maybe I'm missing something. If not, I'm happy to send a pull request.

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.