cognito_idp_rails 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +98 -9
- data/app/controllers/cognito_idp_rails/sessions_controller.rb +27 -12
- data/config/locales/en.yml +6 -0
- data/lib/cognito_idp_rails/configuration.rb +12 -1
- data/lib/cognito_idp_rails/version.rb +1 -1
- data/lib/cognito_idp_rails.rb +10 -5
- metadata +9 -12
- data/app/models/cognito_idp_rails/application_record.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd19a4834a4a544ed409f858802b2587ceb41238b760000f99c64f225e8aa75a
|
|
4
|
+
data.tar.gz: 314efa955ed11cf93b4001acd66bbcd57e91310fc24999760c4ea4b844a43bff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz: '
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0903452da2cff398668f3494f10ba811bed64cd34c90726da50bcaaa307107413d609aceb644c3301ca45912f43435c14e294d4d4e6b39465869a69588e9c286'
|
|
7
|
+
data.tar.gz: bd52e9bbc6bf2d313f9931d1bd63fddd9a0faf81e7f3c20aef0d173c9a07af5936e0d7964fea0b2624c6e7f3df1f381cf60bab38f53269f606e1d835e6e3cbff
|
data/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
# CognitoIdpRails
|
|
1
|
+
# CognitoIdpRails [](https://github.com/appercept/cognito_idp_rails/actions/workflows/main.yml)
|
|
2
2
|
|
|
3
|
-
Simple integration of Amazon Cognito IdP (User Pools) for Rails applications.
|
|
3
|
+
Simple integration of Amazon Cognito IdP (User Pools) for Rails applications. Provides OAuth + PKCE authentication with sensible defaults and minimal configuration.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby >= 3.2.0
|
|
8
|
+
- Rails >= 7.0.0
|
|
9
|
+
- [cognito_idp](https://github.com/appercept/cognito_idp) ~> 1.0 (installed automatically)
|
|
4
10
|
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
@@ -12,17 +18,100 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
12
18
|
|
|
13
19
|
$ gem install cognito_idp_rails
|
|
14
20
|
|
|
15
|
-
##
|
|
21
|
+
## Setup
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
Run the install generator:
|
|
18
24
|
|
|
19
25
|
$ rails generate cognito_idp_rails:install
|
|
20
26
|
|
|
21
|
-
This
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
This will:
|
|
28
|
+
|
|
29
|
+
1. Add a `cognito_idp` route entry to `config/routes.rb`
|
|
30
|
+
2. Create an initializer at `config/initializers/cognito_idp.rb`
|
|
31
|
+
|
|
32
|
+
Review and edit the initializer to match your Amazon Cognito User Pool configuration.
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
CognitoIdpRails.configure do |config|
|
|
38
|
+
config.domain = ENV["COGNITO_DOMAIN"]
|
|
39
|
+
config.client_id = ENV["COGNITO_CLIENT_ID"]
|
|
40
|
+
config.client_secret = ENV["COGNITO_CLIENT_SECRET"]
|
|
41
|
+
|
|
42
|
+
config.scope = "openid" # OAuth scope(s)
|
|
43
|
+
config.after_login_route = "/" # Redirect after login
|
|
44
|
+
config.after_logout_route = "/" # Redirect after logout
|
|
45
|
+
|
|
46
|
+
config.after_login = lambda do |token, user_info, request|
|
|
47
|
+
user = User.where(identifier: user_info.sub).first_or_create do |u|
|
|
48
|
+
u.email = user_info.email
|
|
49
|
+
end
|
|
50
|
+
request.session[:user_id] = user.id
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
config.before_logout = lambda do |request|
|
|
54
|
+
# Runs before the session is reset on logout
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
config.on_login_error = lambda do |error, request|
|
|
58
|
+
# Runs when token exchange or user info retrieval fails
|
|
59
|
+
Rails.logger.error("Login failed: #{error.message}")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Options
|
|
65
|
+
|
|
66
|
+
| Option | Required | Default | Description |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `domain` | Yes | — | Your Cognito User Pool domain (e.g. `your-app.auth.us-east-1.amazoncognito.com`) |
|
|
69
|
+
| `client_id` | Yes | — | The app client ID from your Cognito User Pool |
|
|
70
|
+
| `client_secret` | Yes | — | The app client secret from your Cognito User Pool |
|
|
71
|
+
| `scope` | No | `"openid"` | OAuth scope(s) to request |
|
|
72
|
+
| `after_login_route` | No | `"/"` | Path to redirect to after login |
|
|
73
|
+
| `after_logout_route` | No | `"/"` | Path to redirect to after logout |
|
|
74
|
+
| `after_login` | No | no-op | `lambda { \|token, user_info, request\| }` — called after successful authentication |
|
|
75
|
+
| `before_logout` | No | no-op | `lambda { \|request\| }` — called before session reset on logout |
|
|
76
|
+
| `on_login_error` | No | no-op | `lambda { \|error, request\| }` — called when login fails (receives a `CognitoIdp::Error`) |
|
|
77
|
+
|
|
78
|
+
## Routes
|
|
79
|
+
|
|
80
|
+
The `cognito_idp` route helper adds four routes:
|
|
81
|
+
|
|
82
|
+
| Route | Controller Action | Purpose |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `GET /login` | `sessions#login` | Redirects to Cognito authorization endpoint |
|
|
85
|
+
| `GET /auth/login_callback` | `sessions#login_callback` | Handles the OAuth callback from Cognito |
|
|
86
|
+
| `GET /logout` | `sessions#logout` | Redirects to Cognito logout endpoint |
|
|
87
|
+
| `GET /auth/logout_callback` | `sessions#logout_callback` | Handles the logout callback from Cognito |
|
|
88
|
+
|
|
89
|
+
## How it works
|
|
90
|
+
|
|
91
|
+
CognitoIdpRails implements the OAuth 2.0 Authorization Code flow with PKCE (Proof Key for Code Exchange):
|
|
92
|
+
|
|
93
|
+
1. User visits `/login`
|
|
94
|
+
2. A PKCE code verifier/challenge pair and CSRF state token are generated and stored in the session
|
|
95
|
+
3. User is redirected to your Cognito User Pool's authorization endpoint
|
|
96
|
+
4. After authenticating with Cognito, the user is redirected back to `/auth/login_callback`
|
|
97
|
+
5. The CSRF state is verified, and the authorization code is exchanged for tokens using the PKCE code verifier
|
|
98
|
+
6. User info is fetched from Cognito using the access token
|
|
99
|
+
7. The session is reset, the `after_login` callback is called, and the user is redirected to `after_login_route`
|
|
100
|
+
|
|
101
|
+
On logout, the user is redirected to Cognito's logout endpoint, then back to `/auth/logout_callback` where `before_logout` is called and the session is reset.
|
|
102
|
+
|
|
103
|
+
## Customizing flash messages (i18n)
|
|
104
|
+
|
|
105
|
+
Flash messages are set via i18n. Override them in your application's locale files:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
en:
|
|
109
|
+
cognito_idp_rails:
|
|
110
|
+
sessions:
|
|
111
|
+
login_success: "You have been successfully logged in."
|
|
112
|
+
login_failed: "Login failed."
|
|
113
|
+
logout_success: "You have been successfully logged out."
|
|
114
|
+
```
|
|
26
115
|
|
|
27
116
|
## Development
|
|
28
117
|
|
|
@@ -9,15 +9,16 @@ module CognitoIdpRails
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def login_callback
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
verifier = session.delete(:code_verifier)
|
|
13
|
+
session.delete(:login_state)
|
|
14
|
+
token = client.get_token(grant_type: :authorization_code, code: params[:code], redirect_uri: auth_login_callback_url, code_verifier: verifier)
|
|
15
|
+
user_info = client.get_user_info(token)
|
|
16
|
+
reset_session
|
|
17
|
+
configuration.after_login.call(token, user_info, request)
|
|
18
|
+
redirect_to configuration.after_login_route, notice: I18n.t("cognito_idp_rails.sessions.login_success")
|
|
19
|
+
rescue CognitoIdp::Error => e
|
|
20
|
+
configuration.on_login_error.call(e, request)
|
|
21
|
+
redirect_to configuration.after_login_route, notice: I18n.t("cognito_idp_rails.sessions.login_failed")
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def logout
|
|
@@ -27,13 +28,19 @@ module CognitoIdpRails
|
|
|
27
28
|
def logout_callback
|
|
28
29
|
configuration.before_logout.call(request)
|
|
29
30
|
reset_session
|
|
30
|
-
redirect_to configuration.after_logout_route, notice: "
|
|
31
|
+
redirect_to configuration.after_logout_route, notice: I18n.t("cognito_idp_rails.sessions.logout_success")
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
36
|
def authorization_url
|
|
36
|
-
client.authorization_uri(
|
|
37
|
+
client.authorization_uri(
|
|
38
|
+
redirect_uri: auth_login_callback_url,
|
|
39
|
+
scope: scope,
|
|
40
|
+
state: login_state,
|
|
41
|
+
code_challenge: code_challenge,
|
|
42
|
+
code_challenge_method: "S256"
|
|
43
|
+
)
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
def client
|
|
@@ -48,6 +55,14 @@ module CognitoIdpRails
|
|
|
48
55
|
configuration.scope
|
|
49
56
|
end
|
|
50
57
|
|
|
58
|
+
def code_verifier
|
|
59
|
+
session[:code_verifier] ||= SecureRandom.urlsafe_base64(32)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def code_challenge
|
|
63
|
+
Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier), padding: false)
|
|
64
|
+
end
|
|
65
|
+
|
|
51
66
|
def login_state
|
|
52
67
|
session[:login_state] ||= SecureRandom.urlsafe_base64
|
|
53
68
|
end
|
|
@@ -55,7 +70,7 @@ module CognitoIdpRails
|
|
|
55
70
|
def verify_state
|
|
56
71
|
return if params[:state] == login_state
|
|
57
72
|
|
|
58
|
-
redirect_to configuration.after_login_route, notice: "
|
|
73
|
+
redirect_to configuration.after_login_route, notice: I18n.t("cognito_idp_rails.sessions.login_failed")
|
|
59
74
|
end
|
|
60
75
|
end
|
|
61
76
|
end
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
module CognitoIdpRails
|
|
2
2
|
class Configuration
|
|
3
3
|
attr_accessor :after_login_route, :after_logout_route, :domain, :client_id,
|
|
4
|
-
:client_secret, :after_login, :before_logout, :scope
|
|
4
|
+
:client_secret, :after_login, :before_logout, :on_login_error, :scope
|
|
5
|
+
|
|
6
|
+
REQUIRED_ATTRIBUTES = %i[domain client_id client_secret].freeze
|
|
5
7
|
|
|
6
8
|
def initialize
|
|
7
9
|
@after_login_route = "/"
|
|
8
10
|
@after_logout_route = "/"
|
|
9
11
|
@after_login = lambda { |token, user_info, request| }
|
|
10
12
|
@before_logout = lambda { |request| }
|
|
13
|
+
@on_login_error = lambda { |error, request| }
|
|
14
|
+
@scope = "openid"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate!
|
|
18
|
+
missing = REQUIRED_ATTRIBUTES.select { |attr| public_send(attr).nil? }
|
|
19
|
+
return if missing.empty?
|
|
20
|
+
|
|
21
|
+
raise ConfigurationError, "Missing required configuration: #{missing.join(", ")}"
|
|
11
22
|
end
|
|
12
23
|
end
|
|
13
24
|
end
|
data/lib/cognito_idp_rails.rb
CHANGED
|
@@ -3,6 +3,8 @@ require "cognito_idp_rails/version"
|
|
|
3
3
|
require "cognito_idp"
|
|
4
4
|
|
|
5
5
|
module CognitoIdpRails
|
|
6
|
+
class ConfigurationError < StandardError; end
|
|
7
|
+
|
|
6
8
|
autoload :Configuration, "cognito_idp_rails/configuration"
|
|
7
9
|
|
|
8
10
|
module Routing
|
|
@@ -11,11 +13,14 @@ module CognitoIdpRails
|
|
|
11
13
|
|
|
12
14
|
class << self
|
|
13
15
|
def client
|
|
14
|
-
@client ||=
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
@client ||= begin
|
|
17
|
+
configuration.validate!
|
|
18
|
+
CognitoIdp::Client.new(
|
|
19
|
+
client_id: configuration.client_id,
|
|
20
|
+
client_secret: configuration.client_secret,
|
|
21
|
+
domain: configuration.domain
|
|
22
|
+
)
|
|
23
|
+
end
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
def configuration
|
metadata
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cognito_idp_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Hatherall
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: cognito_idp
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
|
-
- - "
|
|
16
|
+
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: '1.0'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
|
-
- - "
|
|
23
|
+
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: '1.0'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: rails
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -56,8 +55,8 @@ files:
|
|
|
56
55
|
- app/helpers/cognito_idp_rails/application_helper.rb
|
|
57
56
|
- app/jobs/cognito_idp_rails/application_job.rb
|
|
58
57
|
- app/mailers/cognito_idp_rails/application_mailer.rb
|
|
59
|
-
- app/models/cognito_idp_rails/application_record.rb
|
|
60
58
|
- app/views/layouts/cognito_idp_rails/application.html.erb
|
|
59
|
+
- config/locales/en.yml
|
|
61
60
|
- config/routes.rb
|
|
62
61
|
- lib/cognito_idp_rails.rb
|
|
63
62
|
- lib/cognito_idp_rails/configuration.rb
|
|
@@ -74,7 +73,6 @@ metadata:
|
|
|
74
73
|
homepage_uri: https://github.com/appercept/cognito_idp_rails
|
|
75
74
|
source_code_uri: https://github.com/appercept/cognito_idp_rails
|
|
76
75
|
changelog_uri: https://github.com/appercept/cognito_idp_rails/CHANGELOG.md
|
|
77
|
-
post_install_message:
|
|
78
76
|
rdoc_options: []
|
|
79
77
|
require_paths:
|
|
80
78
|
- lib
|
|
@@ -82,15 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
82
80
|
requirements:
|
|
83
81
|
- - ">="
|
|
84
82
|
- !ruby/object:Gem::Version
|
|
85
|
-
version: 2.
|
|
83
|
+
version: 3.2.0
|
|
86
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
85
|
requirements:
|
|
88
86
|
- - ">="
|
|
89
87
|
- !ruby/object:Gem::Version
|
|
90
88
|
version: '0'
|
|
91
89
|
requirements: []
|
|
92
|
-
rubygems_version: 3.
|
|
93
|
-
signing_key:
|
|
90
|
+
rubygems_version: 3.7.2
|
|
94
91
|
specification_version: 4
|
|
95
92
|
summary: Simple Rails integration for Amazon Cognito IdP (User Pools)
|
|
96
93
|
test_files: []
|