{"id":14955446,"url":"https://github.com/npetkov/auth0_rails_api_example","last_synced_at":"2025-08-23T23:31:59.892Z","repository":{"id":14518450,"uuid":"76846148","full_name":"npetkov/auth0_rails_api_example","owner":"npetkov","description":"Sample Rails 6 project demonstrating API authorization with auth0","archived":false,"fork":false,"pushed_at":"2023-01-19T22:49:05.000Z","size":77,"stargazers_count":9,"open_issues_count":5,"forks_count":1,"subscribers_count":1,"default_branch":"dev","last_synced_at":"2024-12-17T11:11:47.438Z","etag":null,"topics":["auth0","cors","jwt","rails-api","rails6"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/npetkov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-19T09:07:53.000Z","updated_at":"2024-11-13T14:59:48.000Z","dependencies_parsed_at":"2023-02-11T22:15:37.790Z","dependency_job_id":null,"html_url":"https://github.com/npetkov/auth0_rails_api_example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npetkov%2Fauth0_rails_api_example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npetkov%2Fauth0_rails_api_example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npetkov%2Fauth0_rails_api_example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npetkov%2Fauth0_rails_api_example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/npetkov","download_url":"https://codeload.github.com/npetkov/auth0_rails_api_example/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230750954,"owners_count":18274970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auth0","cors","jwt","rails-api","rails6"],"created_at":"2024-09-24T13:11:10.181Z","updated_at":"2024-12-21T17:52:13.906Z","avatar_url":"https://github.com/npetkov.png","language":"Ruby","readme":"# README\n\nThis is a sample Rails 6 API project demonstrating API authentication with a JWT\ntoken issued by a third party identity provider. I'm using\n[auth0](https://auth0.com/), but the same principles apply for any OAuth\nprovider, so feel free to change your configuration accordingly. Please note\nthat this project is meant as a proof of concept and shouldn't be used for any\nserious purposes without further consideration.\n\nA separate [project](https://github.com/npetkov/auth0_rails_frontend_example)\nprovides a very basic implementation of an API client (front end) that\ncomplements this one. Although both projects reside in separate repositories,\nthey depend on each other and are quite useless considered on their own.\n\nThe motivation behind this project is to provide a brief, concise introduction\nto JWT authentication using Rails. I've read through a lot of resources covering\ndifferent aspects of the topic so I decided to put them together in one working\nexample.\n\nEven though the documentation at the Auth0 site is quite extensive, it can\nquickly become overwhelming due to the huge amount of configuration options and\nuse cases it covers. Accomplishing a seemingly easy task - authenticating\nagainst an API using OAuth and JWT - has proved to be quite challenging,\nespecially considering some peculiarities of the Ruby gems involved.\n\nIn the following sections, I'll try to summarize all the steps required to get\nthis API project up and running. Of course, having set up Ruby, Rails and the\nrequired Auth0 applications means that setting up the front end project will be\na much quicker task.\n\n## Installing `rbenv` and Ruby\n\nMy preferred method for installing Ruby is via `rbenv`. Please consult the\n[readme file](https://github.com/rbenv/rbenv) for instructions how to install\n`rbenv`, `rbenv-build` and Ruby on your operating system. Below are the\nsteps needed on Ubuntu.\n\n* install `git`, `gcc` and `make` if not already present\n* clone the repo:\n\n`git clone https://github.com/rbenv/rbenv.git ~/.rbenv`\n\n* compile \"dynamic bash extension\":\n\n`cd ~/.rbenv \u0026\u0026 src/configure \u0026\u0026 make -C src`\n\n* set `PATH`:\n\n`echo 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' \u003e\u003e ~/.bashrc`\n\n* run the init script:\n\n`~/.rbenv/bin/rbenv init`\n\n* add output from previous step to shell profile (`.bashrc`):\n\n`echo 'eval \"$(rbenv init -)\"' \u003e\u003e ~/.bashrc`\n\n* reload bash profile:\n\n`source ~/.bashrc`\n\n* install `rbenv-build`:\n\n``` bash\nmkdir -p \"$(rbenv root)\"/plugins\ngit clone https://github.com/rbenv/ruby-build.git \"$(rbenv root)\"/plugins/ruby-build\n```\n\n* List Ruby versions available for install via `rbenv install -l`:\n\n```\n2.5.8\n2.6.6\n2.7.2\n3.0.0\n...\n\nOnly latest stable releases for each Ruby implementation are shown.\nUse 'rbenv install --list-all / -L' to show all local versions.\n```\n\n* Install your preferred Ruby version (I'm using 3.0.0 for this project):\n\n`rbenv install 3.0.0`\n\n* It is possible that the compilation process complains about missing\ndependencies. The script will give useful hints how to install them:\n\n``` bash\nsudo apt install libssl-dev zlib1g-dev\n```\n\n* After installing the missing dependencies and re-running the installation\n  script, set the newly built Ruby version as the default:\n\n`rbenv global 3.0.0`\n\nYou should see a similar output when running `ruby -v`:\n\n```\nruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]\n```\n\n## Installing Rails\n\n* Start by updating RubyGems. This will output a rather verbose changelog, also\n  stating that the `gem`, `bundle` and `bundler` binaries have been installed.\n\n`gem update --system --no-doc`\n\n* Install Rails. On some platforms, gems like `nokogiri` may fail to install\n  from the get go as they rely on native extensions which may require\n  platform-specific tweaks to compile. These issues are rather common and\n  there are a lot of resources online explaining how to resolve them.\n\n`gem install rails`\n\n* When finished, double-check: `gem info rails`\n\n## Setting up an Auth0 application and API\n\nSigning up for an Auth0 account is rather straightforward, but there is one\ncaveat: use a regular username/password to register if you plan on adding social\nintegrations (e.g. Google oauth) to your account later. Signing up using \"sign\nin with Google\" and creating a Google \"social connection\" after that has proved\ntroublesome for me in the past.\n\nThere are several things which must be configured in the dashboard: a new\napplication, a new API and user groups/permissions.\n\n### Creating a new application\n\nChoose \"Applications\" in the sidebar and then proceed with creating a new\napplication. The following configuration is required for the new app.\n\n* Name - e.g. \"Rails Application\"\n* Application Type - choose \"Regular Web Applications\"\n* Navigate away from \"Quick start\" to the \"Settings\" tab\n* In the \"Allowed callback URLs\" text area, enter\n  `http://localhost:3000/auth/auth0/callback`\n\n  If you plan on running your (local) server at a different port, change it\n  accordingly.\n* In the \"Allowed logout URLs\" text area, enter `http://localhost:3000`\n* Enter the same URL in \"Allowed Web Origins\" and \"Allowed Origins (CORS)\"\n* Navigate to the bottom of the page and choose \"Show Advanced Settings\"\n* In the OAuth tab, change the \"JsonWebToken Signature Algorithm\" to `HS256`\n* Save your changes.\n\n### Creating a new API\n\nFrom the sidebar, choose \"APIs\" and proceed by clicking the \"Create API\" button\non the far right. The following configuration must be applied:\n\n* Name - e.g. \"Rails API\"\n* Identifier - can be anything, but should preferably be an URL, as the hint\n  suggests. This identifier will appear in the `aud` claim of the access tokens\n  issued by Auth0 and used to access the API\n* Signing algorithm: choose `HS256`\n* Navigate away from the \"Quick Start\" tab to \"Settings\"\n* Enable both toggles under \"RBAC Setting\" - \"Enable RBAC\" and \"Add Permissions in\n  the Access Token\" and save your changes\n* In the \"Permissions\" tab, create two new permissions - `index:notes` and\n  `create:notes`. Those are the only actions that the single Rails API endpoint\n  exposes.\n* In the \"Machine to Machine Applications\" tab, enable the toggle next to the\n  application you created in the section above (e.g. Rails Application).\n* Click on the arrow next to the toggle and select both permissions you just\n  created.\n* Click on update and confirm that you create a grant with all available scopes.\n\n### Creating user roles\n\n* Choose \"Users and Roles\" from the side bar\n* Create two new roles - e.g. \"User\" and \"Administrator\", selecting different\n  permissions accordingly\n* If you have already registered/created some users, you can proceed with\n  assigning them some of the newly created roles.\n\n### Creating a social connection - allowing \"sign in with Google\" for your application\n\nThis step is optional, but it has saved me a lot of time during development.\nFrom the \"Connections\" menu in the sidebar, choose \"Social\" and then add\n\"Google/Gmail\". The\n[documentation](https://auth0.com/docs/connections/social/google?_ga=2.151122937.1511962912.1611782875-1030349018.1611782874)\nAuth0 provides on the topic is quite adequate and I refer you to the steps\ndescribed there for creating the configuration needed on the Google side of the\nfence.\n\nOnce you're done, double check that the connection is enabled for the new \"Rails\nApplication\" created in the section above.\n\n## Project setup\n\nClone the repo and run the usual `bundle install`. I'm using Ruby 3.0.0 and\ncan't guarantee that everything will work as expected with earlier versions.\n\nIf you encounter and error with the `sqlite3` gem installation, follow the hints\nin the error description. Under Ubuntu, the following package must be installed:\n\n``` bash\nsudo apt install libsqlite3-dev\n```\n\nOnce all gems have been successfully installed, run `bin/setup`. This will\nensure everything is properly set up and you can launch the application server.\n\nTo launch the server, use `bin/rails s -p 3001`. I prefer using port 3001 as I\nnormally have another server listening to port 3000. Below, I'll assume the\nserver is running at `http://localhost:3001`.\n\n## Auth0 and API secret configuration\n\nThe project uses the Rails 6 encrypted credentials scheme. The project relies on\nthe following credentials being present:\n\n* __api_secret__ - this is the value used to sign the JWT tokens which API will\n  use for authentication. The value must be copied from the \"Signing Secret\"\n  (read-only) input field in the \"Token Settings\" section of your Auth0 API.\n* __api_identifier__ - the identifier used for the auth0 API project. It will\n  appear in the `aud` claim of the access tokens issued by Auth0 and used to\n  access the API.\n\nIn order to edit the Rails application credentials, run `bin/rails credentials:edit`\nin the project root. If Rails complains about an unset `EDITOR` environmental\nvariable, append one to your shell profile and then source it to apply the\nchanges:\n\n``` bash\necho 'export EDITOR=vim' \u003e\u003e ~/.bashrc\nsource ~/.bashrc\n```\n\nPlease note that the master key used to encrypt the credentials is not under\nversion control. If you lose it, you'll have to recreate the credentials store.\n\n## CORS\n\nThe server is configured to allow CORS requests from `http://localhost:3000` by\ndefault. This can be changed in `config/initializers/cors.rb`.\n\n## Authentication flow\n\nThe API authentication relies solely on an `Authorization` header, which must be\nsupplied with every request. The current implementation of the verification hook\nchecks verifies  the \"issued at\" and \"audience\" claim (beyond the token\nsignature).\n\n``` ruby\nbefore_action :verify_token\nafter_action  :verify_authorized\n\ndef verify_token\n  auth_header = request.headers['Authorization'] || ''\n  token = auth_header.split.last\n  options = {\n    aud: Rails.credentials.application.api_identifier,\n    verify_aud: true,\n    verify_iat: true,\n    algorithm: 'HS256'\n  }\n  begin\n    @token = JWT.decode(token, Rails.application.credentials.api_secret, true, options)[0]\n  rescue JWT::DecodeError =\u003e e\n    response.headers.merge!(api_unauthenticated(e))\n    head 401\n  end\nend\n```\n\n## Authorization\n\nI've also included [Pundit](https://github.com/elabs/pundit) in the project to\ndemonstrate how per-endpoint authorization can be achieved via JWT. Let's take a\nlook at an (decoded) API access token issued by Auth0:\n\n``` json\n{\n  \"iss\": \"https://\u003c\u003ctenant\u003e\u003e.auth0.com/\",\n  \"sub\": \"google-oauth2|123456\",\n  \"aud\": [\n    \"\u003c\u003capi-identifier\u003e\u003e\"\n  ],\n  \"iat\": 1611689366,\n  \"exp\": 1611775766,\n  \"azp\": \"\u003c\u003capplication-identifier\u003e\u003e\",\n  \"scope\": \"openid profile email index:notes create:notes\",\n  \"permissions\": [\n    \"create:notes\",\n    \"index:notes\"\n  ]\n}\n```\n\nAs consequence of enabling RBAC and toggling the \"Add Permissions in the Access\nToken\" switch for the Auth0 API, the issued token contains all the permissions\nthat the current user (denoted in the `sub` claim) has been granted.\n\nNote that the `scopes` claim contains the scopes (permissions) that have been\nrequested during the authorization phase. The actual permissions granted to the\nused and listed in the `permissions` claim may be restricted, depending on the\nrole the user has been assigned. In order to achieve this, you have to define a\n[rule](https://auth0.com/docs/architecture-scenarios/spa-api/part-2#create-a-rule-to-validate-token-scopes) in the Auth0 dashboard.\n\nIn the `ActionScope` class, the permission provided in the token are parsed into\na map, which can then be used together with some Pundit magic to provide\nauthorization to the API endpoint(s).\n\n``` ruby\ndef map_permissions\n  hash = Hash.new { |map, key| map[key] = [] }\n  token[:permissions].each_with_object(hash) do |permission, map|\n    action, resource = permission.split(':')\n    map[resource] \u003c\u003c action\n  end\nend\n```\n\nThe final result is that API endpoints can be declared in an extremely concise manner:\n\n``` ruby\nclass NotesController \u003c ApplicationController\n  def index\n    notes = policy_scope Note\n    authorize notes\n    render json: notes.to_json, status: 200\n  end\n\n  def create\n    note = Note.new\n    authorize note\n    note.assign_attributes(permitted_attributes(note))\n    status = note.save ? 201 : 422\n    render json: note.to_json, status: status\n  end\nend\n```\n## Testing\n\nI haven't implemented any tests yet so please feel free to contribute.\n\n## Contributing\n\nDon't hesitate to create issues or feature requests. Any suggestions are\nwelcome.\n\n## External links\n\nBelow are some authentication/authorization resources I found quite useful.\n\n  * [OAuth2 overview by\n    Google](https://developers.google.com/identity/protocols/OAuth2)\n  * [OAuth2 code\n    grant](https://oauthlib.readthedocs.io/en/latest/oauth2/grants/authcode.html)\n  * [JWT and APIs in the auth0\n    blog](https://auth0.com/blog/2014/12/02/using-json-web-tokens-as-api-keys/)\n  * [JWT vulnerabilities in the auth0\n    blog](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/)\n  * [The go-to JWT ruby library, with a nice claims\n    overview/examples](https://github.com/jwt/ruby-jwt)\n  * [The WWW-Authenticate Response Header\n    Field](https://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html#authn-header)\n  * [CSRF\n    Tokens](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Synchronizer_.28CSRF.29_Tokens)\n  * [Cookies and CORS](https://quickleft.com/blog/cookies-with-my-cors/)\n  * [OPTIONS requests in\n    Rails](https://bibwild.wordpress.com/2014/10/07/catching-http-options-request-in-a-rails-app/)\n  * [Some useful CORS practices for Rails\n    projects](https://gist.github.com/dhoelzgen/cd7126b8652229d32eb4)\n\n## Disclaimer\n\nI am not part of the auth0 team nor am I affiliated to auth0 in any way. I'm\nusing auth0 for the sole purpose of demonstrating API authorization via JWT.\n\n## License\n\nThis product is licensed under the [MIT\nLicense](https://github.com/npetkov/auth0_rails_api_example/blob/dev/LICENSE).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnpetkov%2Fauth0_rails_api_example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnpetkov%2Fauth0_rails_api_example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnpetkov%2Fauth0_rails_api_example/lists"}