Skip to content

Commit 2c1a7de

Browse files
committed
implement basic authorization with auth0
1 parent 910e1ff commit 2c1a7de

12 files changed

Lines changed: 155 additions & 22 deletions

File tree

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ end
3131
group :development do
3232
gem 'dotenv-rails'
3333
end
34+
35+
gem 'jwt', '~> 2.5'

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ GEM
8989
irb (1.7.4)
9090
reline (>= 0.3.6)
9191
json (2.6.3)
92+
jwt (2.7.1)
9293
language_server-protocol (3.17.0.3)
9394
loofah (2.21.3)
9495
crass (~> 1.0.2)
@@ -192,6 +193,7 @@ DEPENDENCIES
192193
bootsnap
193194
debug
194195
dotenv-rails
196+
jwt (~> 2.5)
195197
puma (~> 5.0)
196198
rack-cors
197199
rails (~> 7.0.3, >= 7.0.3.1)

NOTICE

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ ________________________________________________________________________________
7272
- io-console (0.6.0) [Ruby, BSD-2-Clause]
7373
- irb (1.7.4) [BSD-2-Clause]
7474
- json (2.6.3) [Ruby]
75+
- jwt (2.7.1) [MIT]
7576
- language_server-protocol (3.17.0.3) [MIT]
7677
- loofah (2.21.3) [MIT]
7778
- mail (2.8.1) [MIT]
@@ -1008,6 +1009,24 @@ Ruby
10081009
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
10091010

10101011

1012+
--------------------------------------------------------------------------------
1013+
Package Title: jwt (2.7.1)
1014+
--------------------------------------------------------------------------------
1015+
1016+
* Declared Licenses *
1017+
MIT
1018+
1019+
1020+
Copyright (c) 2011 Jeff Lindsay
1021+
1022+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1023+
1024+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1025+
1026+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1027+
1028+
1029+
10111030
--------------------------------------------------------------------------------
10121031
Package Title: language_server-protocol (3.17.0.3)
10131032
--------------------------------------------------------------------------------
@@ -7056,6 +7075,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
70567075
THE SOFTWARE.
70577076

70587077

7078+
* MIT *
7079+
7080+
Copyright (c) 2011 Jeff Lindsay
7081+
7082+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7083+
7084+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7085+
7086+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7087+
7088+
70597089
* MIT *
70607090

70617091
Copyright (c) 2011-2013 Peter Zotov <whitequark@whitequark.org>
@@ -8911,6 +8941,10 @@ purpose. It is provided "as is" without express or implied warranty.
89118941

89128942
This is a catch-all license used when we find many different proprietary licenses.
89138943

8944+
* public-domain *
8945+
8946+
No license text available
8947+
89148948

89158949
--------------------------------------------------------------------------------
89168950
--------------------------------------------------------------------------------

README.md

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
# Rails/Ruby: Starter API Code Sample
1+
# Rails/Ruby: API Basic Access Control Code Sample
22

3-
This Ruby code sample demonstrates how to build an API server using Rails that is secure by design.
3+
This Ruby code sample demonstrates **how to implement authorization** in Rails API servers using Auth0.
44

5-
Visit the ["Rails/Ruby Code Samples: API Security in Action"](https://developer.auth0.com/resources/code-samples/api/railss) section of the ["Auth0 Developer Resources"](https://developer.auth0.com/resources) to explore how you can secure Rails applications written in Ruby by implementing endpoint protection and authorization with Auth0.
5+
This code sample is part of the ["Auth0 Developer Resources"](https://developer.auth0.com/resources), a place where you can explore the authentication and authorization features of the Auth0 Identity Platform.
6+
7+
Visit the ["Rails/Ruby Code Sample: Authorization For Basic APIs"](https://developer.auth0.com/resources/code-samples/api/rails/basic-authorization) page for instructions on how to configure and run this code sample and how to integrate it with a Single-Page Application (SPA) of your choice.
68

79
## Why Use Auth0?
810

911
Auth0 is a flexible drop-in solution to add authentication and authorization services to your applications. Your team and organization can avoid the cost, time, and risk that come with building your own solution to authenticate and authorize users. We offer tons of guidance and SDKs for you to get started and [integrate Auth0 into your stack easily](https://developer.auth0.com/resources/code-samples/full-stack).
10-
11-
## Set Up and Run the Rails Project
12-
13-
Install the project dependencies:
14-
15-
```bash
16-
bundle install
17-
```
18-
19-
Run the application:
20-
21-
```bash
22-
bundle exec rails s
23-
```
24-
25-
Visit [`http://localhost:6060/`](http://localhost:6060/) to access the starter application.

app/controllers/api/base_controller.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
module Api
44
class BaseController < ApplicationController
5+
include Secured
6+
57
rescue_from StandardError do |e|
68
render json: { message: e.message }, status: :internal_server_error
79
end

app/controllers/api/messages_controller.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
module Api
44
class MessagesController < BaseController
5+
before_action :authorize, except: [:public]
6+
57
def admin
68
render json: Message.admin_message
79
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
3+
module Secured
4+
extend ActiveSupport::Concern
5+
6+
REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze
7+
BAD_CREDENTIALS = {
8+
message: 'Bad credentials'
9+
}.freeze
10+
MALFORMED_AUTHORIZATION_HEADER = {
11+
error: 'invalid_request',
12+
error_description: 'Authorization header value must follow this format: Bearer access-token',
13+
message: 'Bad credentials'
14+
}.freeze
15+
16+
def authorize
17+
token = token_from_request
18+
19+
return if performed?
20+
21+
validation_response = Auth0Client.validate_token(token)
22+
23+
return unless (error = validation_response.error)
24+
25+
render json: {message: error.message}, status: error.status
26+
end
27+
28+
private
29+
30+
def token_from_request
31+
authorization_header_elements = request.headers['Authorization']&.split
32+
33+
render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements
34+
35+
unless authorization_header_elements.length == 2
36+
render json: MALFORMED_AUTHORIZATION_HEADER, status: :unauthorized and return
37+
end
38+
39+
scheme, token = authorization_header_elements
40+
41+
render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer'
42+
43+
token
44+
end
45+
end

app/lib/auth0_client.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
require 'jwt'
4+
require 'net/http'
5+
6+
class Auth0Client
7+
8+
# Auth0 Client Objects
9+
Error = Struct.new(:message, :status)
10+
Response = Struct.new(:decoded_token, :error)
11+
12+
# Helper Functions
13+
def self.domain_url
14+
"https://#{Rails.configuration.auth0.domain}/"
15+
end
16+
17+
def self.decode_token(token, jwks_hash)
18+
JWT.decode(token, nil, true, {
19+
algorithm: 'RS256',
20+
iss: domain_url,
21+
verify_iss: true,
22+
aud: Rails.configuration.auth0.audience.to_s,
23+
verify_aud: true,
24+
jwks: { keys: jwks_hash[:keys] }
25+
})
26+
end
27+
28+
def self.get_jwks
29+
jwks_uri = URI("#{domain_url}.well-known/jwks.json")
30+
Net::HTTP.get_response jwks_uri
31+
end
32+
33+
# Token Validation
34+
def self.validate_token(token)
35+
jwks_response = get_jwks
36+
37+
unless jwks_response.is_a? Net::HTTPSuccess
38+
error = Error.new(message: 'Unable to verify credentials', status: :internal_server_error)
39+
return Response.new(nil, error)
40+
end
41+
42+
jwks_hash = JSON.parse(jwks_response.body).deep_symbolize_keys
43+
44+
decoded_token = decode_token(token, jwks_hash)
45+
46+
Response.new(decoded_token, nil)
47+
rescue JWT::VerificationError, JWT::DecodeError => e
48+
error = Error.new('Bad credentials', :unauthorized)
49+
Response.new(nil, error)
50+
end
51+
end

config/application.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# you've limited to :test, :development, or :production.
2121
Bundler.require(*Rails.groups)
2222

23-
module ApiRailsRubyHelloWorldCarla
23+
module ApiRailsRubyHelloWorld
2424
class Application < Rails::Application
2525
# Initialize configuration defaults for originally generated Rails version.
2626
config.load_defaults 7.0
@@ -40,6 +40,8 @@ class Application < Rails::Application
4040

4141
config.exceptions_app = routes
4242

43+
config.auth0 = config_for(:auth0)
44+
4345
config.action_dispatch.default_headers = {
4446
'X-Frame-Options' => 'deny',
4547
'X-XSS-Protection' => '0',

config/auth0.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
development:
2+
domain: <%= ENV.fetch('AUTH0_DOMAIN') %>
3+
audience: <%= ENV.fetch('AUTH0_AUDIENCE') %>
4+
5+
production:
6+
domain: <%= ENV.fetch('AUTH0_DOMAIN') %>
7+
audience: <%= ENV.fetch('AUTH0_AUDIENCE') %>

0 commit comments

Comments
 (0)