Git Product home page Git Product logo

openid_connect's Introduction

OpenIDConnect

Client library for consuming and working with OpenID Connect Providers

OpenIDConnect is built and maintained by DockYard, contact us for expert Elixir and Phoenix consulting.

Installation

Available in Hex, the package can be installed as:

Add openid_connect to your list of dependencies in mix.exs:

def deps do
  [{:openid_connect, "~> 1.0.0"}]
end

Getting Started

Configuration

Most of the functions expect a config map which means application developer should take care for the storage of those options, eg:

google_config = %{
  discovery_document_uri: "https://accounts.google.com/.well-known/openid-configuration",
  client_id: "CLIENT_ID",
  client_secret: "CLIENT_SECRET",
  redirect_uri: "https://example.com/session",
  response_type: "code",
  scope: "openid email profile"
}

authorization_uri(google_config)

Most major OAuth2 providers have added support for OpenIDConnect. See a short list of most major adopters of OpenIDConnect.

Usage

In your app code you will need to do a few things:

  1. Expose the authorization_uri for the provider(s)
  2. Have your app handle the redirect from the provider
  3. Fetch the JWT
  4. Verify the JWT from the provider
  5. Read the claim payload to set whatever session data your app requires

Exposing the authorization uri

You can build the correct authorization URI for a provider with:

OpenIDConnect.authorization_uri(google_config)

In this case we are requesting that OpenIDConnect build the authorization URI for the google provider. You should use this URI for your users to link out to for authenticating with the given provider

Handling the redirect from the provider

You will need an endpoint for the provider to redirect to after the user has authenticated. This is where the remainder of your steps will take place.

Fetch the JWT

The JSON Web Token (JWT) must be fetched, using the key/value pairs from the response_type params that were part of the redirect to your application:

{:ok, tokens} = OpenIDConnect.fetch_tokens(google_config, %{code: params["code"]})

Verify the JWT

The JWT is encrypted and it should always be verified with the JSON Web Keys (JWK) for the provider:

{:ok, claims} = OpenIDConnect.verify(google_config, tokens["id_token"])

The claims is a payload with the information from the scopes you requested of the provider.

Read the claims and set your app's user session

Now that you have the claims payload you can use the user data to identify and set the user's session state for your app. Setting your app's session state is outside the scope of this library.

Phoenix Example

# router.ex
get("/session", SessionController, :create)
get("/session/authorization-uri", SessionController, :authorization_uri)

# session_controller.ex
# you could also take the `provider` as a query param to pass into the function
def authorization_uri(conn, _params) do
  google_config = Application.fetch_env!(:my_app, :google_oidc_config)
  {:ok, uri} = OpenIDConnect.authorization_uri(google_config)
  
  json(conn, %{uri: uri})
end

# The `Authentication` module here is an imaginary interface for setting session state
def create(conn, params) do
  google_config = Application.fetch_env!(:my_app, :google_oidc_config)

  with {:ok, tokens} <- OpenIDConnect.fetch_tokens(google_config, params["code"]),
       {:ok, claims} <- OpenIDConnect.verify(google_config, tokens["id_token"]),
       {:ok, user} <- Authentication.call(google_config, Repo, claims) do

    conn
    |> put_status(200)
    |> render(UserView, "show.html", data: user)
  else
    _ -> send_resp(conn, 401, "")
  end
end

Authors

We are very thankful for the many contributors

Versioning

This library follows Semantic Versioning

Looking for help with your Elixir project?

At DockYard we are ready to help you build your next Elixir project. We have a unique expertise in Elixir and Phoenix development that is unmatched. Get in touch!

At DockYard we love Elixir! You can read our Elixir blog posts or come visit us at The Boston Elixir Meetup that we organize.

Want to help?

Please do! We are always looking to improve this library. Please see our Contribution Guidelines on how to properly submit issues and pull requests.

Legal

DockYard, Inc. ยฉ 2018

@dockyard

Licensed under the MIT license

openid_connect's People

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openid_connect's Issues

(EXIT) no process:

iex(1)> OpenIDConnect.authorization_uri(:xero)
** (exit) exited in: GenServer.call(:openid_connect, {:discovery_document, :xero}, 5000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
(elixir 1.10.2) lib/gen_server.ex:1013: GenServer.call/3
(openid_connect 0.2.2) lib/openid_connect.ex:89: OpenIDConnect.authorization_uri/3
iex(1)>

OpenID claims validation

Thanks for creating this library, the integration into my project was a breeze but I was surprised that OpenIDConnect.verify/3 is successful when given an expired token.

Shouldn't the documentation explicitly state that verify/3 only checks the token signature and that it's up to the application to validate the token claims?

I understand that, to some degree, claim validation is an application concern but the OpenID spec lists a handful of required ID Token claims, among which are exp and aud. Wouldn't it make sense for an OpenID Connect implementation to validate those standard claims?

Proper Supervisor format for Elixir 1.7.4?

All the documentation refers to deprecated syntax for adding the Supervisor worker to your project's
application.ex file:

worker(OpenIDConnect.Worker, [Application.get_env(:my_app, :openid_connect_providers)])

which will produce the compile error:

** (CompileError) lib/eos/application.ex:19: undefined function worker/2

According to https://hexdocs.pm/elixir/Supervisor.html the new syntax is to either use a tuple:

{OpenIDConnect.Worker, [Application.get_env(:eos, :openid_connect_providers)]}

but that produces the error:

** (Mix) Could not start application eos: Eos.Application.start(:normal, []) returned an error: shutdown: failed to start child: OpenIDConnect.Worker
** (EXIT) an exception was raised:
** (FunctionClauseError) no function clause matching in anonymous fn/1 in OpenIDConnect.Worker.init/1
(openid_connect) lib/openid_connect/worker.ex:22: anonymous fn([hcp: [discovery_document_uri: "https://gateway.sso.homecarepulse.com/hcp/oidc/.well-known/openid-configuration", client_id: "hcp-jason-dev", client_secret: "", redirect_uri: "http://eos.hcp-jason-dev.local:4000", scope: "openid email profile"]]) in OpenIDConnect.Worker.init/1
(elixir) lib/map.ex:210: Map.new_transform/3
(openid_connect) lib/openid_connect/worker.ex:22: OpenIDConnect.Worker.init/1
(stdlib) gen_server.erl:374: :gen_server.init_it/2
(stdlib) gen_server.erl:342: :gen_server.init_it/6
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

or to use a map:
%{ id: OpenIDConnect.Worker, start: { OpenIDConnect.Worker, :start_link, [Application.get_env(:eos, :openid_connect_providers)] } }

, but that produces the error:

** (Mix) Could not start application eos: Eos.Application.start(:normal, []) returned an error: shutdown: failed to start child: OpenIDConnect.Worker
** (EXIT) an exception was raised:
** (MatchError) no match of right hand side value: {:error, :update_documents, %HTTPoison.Error{id: nil, reason: :econnrefused}}
(openid_connect) lib/openid_connect/worker.ex:55: OpenIDConnect.Worker.update_documents/2
(openid_connect) lib/openid_connect/worker.ex:23: anonymous fn/1 in OpenIDConnect.Worker.init/1
(elixir) lib/map.ex:210: Map.new_transform/3
(openid_connect) lib/openid_connect/worker.ex:22: OpenIDConnect.Worker.init/1
(stdlib) gen_server.erl:374: :gen_server.init_it/2
(stdlib) gen_server.erl:342: :gen_server.init_it/6
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

I've also tried various permutations with an additional set of square brackets on worker init argument and such just in case, but no luck. I can also tell by the first error that it is able to read the environment config. It has to be some silly little syntax issue on my part, but I'm new to Phoenix/Elixir and am running out of ideas. Thoughts? (Thanks in advance)

Handling failures?

The app I'm maintaining references the OpenIDConnect.Worker directly in the app supervisor. Recently, I was working on the app while my internet was down. I was very surprised that my app failed to start and it seemed to be because the worker itself was failing to start (because the update_documents call was failing).

I also tested an 'intermittent' failure by using a network monitoring tool to block connections to intuit.com and then manually triggering the worker 'update documents' action (by sending a suitable message to the worker process with my app running under iex). I was again surprised that the (expected) failure of the worker caused my app itself to shutdown (and almost immediately too).

I'm still pretty new to Elixir and Erlang/OTP so I'm not sure whether this is 'reasonable' and that I should expect to handle failures myself, or whether the Worker module itself would (at least ideally) gracefully handle errors connecting to the configured OpenID Connect providers.

determine next cert refresh time from the Expires header

the cert endpoint response Expires header has a GMT of 6 hours. When we fall outside this window our stored public key gets into a bad spot. We should read value and use that to use on send_after to ensure the next time to refresh.

Code-for-token request isn't authenticated

Hi there ๐Ÿ‘‹
First of all, thanks for this!

I'm trying to use this library locally against a mock OIDC Server (https://github.com/appvia/mock-oidc-user-server), that uses the node-oidc-provider under the hood.

What I'm seeing is that the fetch_tokens/3 function doesn't do any authentication, hence the code-for-token request fails with a 401. Full error details below:

    ** (MatchError) no match of right hand side value: {:error, :fetch_tokens, %HTTPoison.Response{body: "{\"error\":\"invalid_client\",\"error_description\":\"client authentication failed\"}", headers: [{"Pragma", "no-cache"}, {"Cache-Control", "no-cache, no-store"}, {"Content-Type", "application/json; charset=utf-8"}, {"Content-Length", "77"}, {"Date", "Sat, 24 Aug 2019 11:46:30 GMT"}, {"Connection", "keep-alive"}], request: %HTTPoison.Request{body: {:form, [client_id: "my-client", client_secret: "my-secret", code: "gU9qoumrVmlaFzOe3JI6ri6KYXG", grant_type: "authorization_code", redirect_uri: "http://localhost:4003/session"]}, headers: [{"Content-Type", "application/x-www-form-urlencoded"}], method: :post, options: [], params: %{}, url: "http://oidc:9090/token"}, request_url: "http://oidc:9090/token", status_code: 401}}

If I change the headers set by the fetch_tokens/3 function like this:

+    basic_auth = Base.encode64("my-client:my-secret")

    headers = [
      {"Content-Type", "application/x-www-form-urlencoded"},
+     {"Authorization", "Basic #{basic_auth}"},
    ]

The fetch_tokens/3 function works as expected.

Since the OIDC specifies that this request should be authenticated, using HTTP Basic or JWT-based authentication, isn't this missing from this library?

I'll open a PR to fix this if it's needed, I'd just like to confirm this with you first.

Thanks in advance ๐Ÿ™Œ

New release

Hey team first I would like to show gratitude for the work that has been done on this library specially for the rework done recently by the Firezone team (#50) as many issues were solved.

I've been using the current master branch and it has been working quite well but I would love to lock in a specific semver version etc. Is there any plan to release a new version on hex?

Thanks in advance

Provide API for getting Endpoint Configuration

Thanks for a nice library!

For example there's the end_session_endpoint in Endpoint Configuration which is required to log user out. It would be nice to be able to get values from Endpoint Configuration through openid_connect API.

Don't use config.exs for provider configuration

These configurations do not change the build.
Things like discovery_urls are likely to be runtime dependent, and potentially set from environment variables.

The examples should show a different way of doing this. For now I have just put my configuration in application.ex

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.