auth_rails 1.0.2 → 1.1.1

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.
@@ -0,0 +1,152 @@
1
+ # CLI to generate Configuration
2
+
3
+ ## Default Option
4
+
5
+ ```sh
6
+ rails g auth_rails
7
+ ```
8
+
9
+ This will create a default configuration for AuthRails.
10
+
11
+ ```rb
12
+ # frozen_string_literal: true
13
+
14
+ AuthRails.configure do |config|
15
+ config.jwt do |jwt|
16
+ jwt.access_token do |access_token|
17
+ access_token.exp = 1.hour.since
18
+ access_token.secret_key = ENV.fetch('JWT_SECRET', '')
19
+ end
20
+
21
+ # jwt.strategy = AuthRails::Strategies::AllowedTokenStrategy
22
+
23
+ # if you wanna use refresh token
24
+ # uncomment those lines below
25
+ # jwt.refresh_token do |refresh_token|
26
+ # refresh_token.http_only = true
27
+ # refresh_token.exp = 1.year.since
28
+ # refresh_token.algorithm = 'HS256'
29
+ # refresh_token.cookie_key = :ref_tok
30
+ # refresh_token.secret_key = ENV.fetch('JWT_SECRET', '')
31
+ # end
32
+ end
33
+ end
34
+
35
+ Rails.application.config.to_prepare do
36
+ AuthRails.configure do |config|
37
+ config.resource_class = User
38
+
39
+ # if you wanna use custom error classes
40
+ # uncomment code below
41
+ # config.error_class = AuthError
42
+ end
43
+ end
44
+ ```
45
+
46
+ ## Strategy Option
47
+
48
+ ```sh
49
+ rails g auth_rails --strategy allowed_token
50
+ ```
51
+
52
+ This will create a configuration and enable strategy `AuthRails::Strategies::AlloedTokenStrategy` as default.
53
+
54
+ ```rb
55
+ # frozen_string_literal: true
56
+
57
+ AuthRails.configure do |config|
58
+ config.jwt do |jwt|
59
+ jwt.access_token do |access_token|
60
+ access_token.exp = 1.hour.since
61
+ access_token.secret_key = ENV.fetch('JWT_SECRET', '')
62
+ end
63
+
64
+ jwt.strategy = AuthRails::Strategies::AllowedTokenStrategy
65
+
66
+ # remember uncomment those ones
67
+ jwt.refresh_token do |refresh_token|
68
+ refresh_token.http_only = true
69
+ refresh_token.exp = 1.year.since
70
+ refresh_token.algorithm = 'HS256'
71
+ refresh_token.cookie_key = :ref_tok
72
+ refresh_token.secret_key = ENV.fetch('JWT_SECRET', '')
73
+ end
74
+ end
75
+ end
76
+
77
+ Rails.application.config.to_prepare do
78
+ AuthRails.configure do |config|
79
+ config.resource_class = User
80
+
81
+ # if you wanna use custom error classes
82
+ # uncomment code below
83
+ # config.error_class = AuthError
84
+ end
85
+ end
86
+ ```
87
+
88
+ You must modify User model to make this works.
89
+
90
+ ```rb
91
+ # app/models/user.rb
92
+ # frozen_string_literal: true
93
+
94
+ class User < ApplicationRecord
95
+ include AuthRails::Concerns::AllowedTokenStrategy
96
+
97
+ has_secure_password
98
+ end
99
+ ```
100
+
101
+ ## Model Option
102
+
103
+ ```sh
104
+ rails g auth_rails --model CustomUser
105
+ ```
106
+
107
+ This will create a configuration with the `resource_class` as `CustomUser`.
108
+
109
+ ```rb
110
+ # frozen_string_literal: true
111
+
112
+ AuthRails.configure do |config|
113
+ config.jwt do |jwt|
114
+ jwt.access_token do |access_token|
115
+ access_token.exp = 1.hour.since
116
+ access_token.secret_key = ENV.fetch('JWT_SECRET', '')
117
+ end
118
+
119
+ # jwt.strategy = AuthRails::Strategies::AllowedTokenStrategy
120
+
121
+ # if you wanna use refresh token
122
+ # uncomment those lines below
123
+ # jwt.refresh_token do |refresh_token|
124
+ # refresh_token.http_only = true
125
+ # refresh_token.exp = 1.year.since
126
+ # refresh_token.algorithm = 'HS256'
127
+ # refresh_token.cookie_key = :ref_tok
128
+ # refresh_token.secret_key = ENV.fetch('JWT_SECRET', '')
129
+ # end
130
+ end
131
+ end
132
+
133
+ Rails.application.config.to_prepare do
134
+ AuthRails.configure do |config|
135
+ config.resource_class = CustomUser
136
+
137
+ # if you wanna use custom error classes
138
+ # uncomment code below
139
+ # config.error_class = AuthError
140
+ end
141
+ end
142
+ ```
143
+
144
+ Remember to modify the `CustomUser` class.
145
+
146
+ ```rb
147
+ # frozen_string_literal: true
148
+
149
+ class CustomUser < ApplicationRecord
150
+ has_secure_password
151
+ end
152
+ ```
@@ -0,0 +1,59 @@
1
+ # CLI to generate Migration
2
+
3
+ This CLI always need to provide a strategy option to know which migration file should be created.
4
+
5
+ ## Default Option
6
+
7
+ ```sh
8
+ rails g auth_rails:migration --strategy allowed_token
9
+ ```
10
+
11
+ This will create a migration file for `AllowedToken` model.
12
+
13
+ ```rb
14
+ # frozen_string_literal: true
15
+
16
+ class CreateAllowedTokens < ActiveRecord::Migration[7.1]
17
+ def change
18
+ create_table :allowed_tokens do |t|
19
+ t.string :jti, null: false
20
+ t.string :aud
21
+ t.datetime :exp, null: false
22
+
23
+ t.timestamps
24
+
25
+ t.references :user, foreign_key: { on_delete: :cascade }, null: false
26
+
27
+ t.index %i[jti aud]
28
+ end
29
+ end
30
+ end
31
+ ```
32
+
33
+ ## Model Option
34
+
35
+ ```sh
36
+ rails g auth_rails:migration --strategy allowed_token --model CustomUser
37
+ ```
38
+
39
+ This will create a migration file for `AllowedToken` model and add reference with `CustomUser`.
40
+
41
+ ```rb
42
+ # frozen_string_literal: true
43
+
44
+ class CreateAllowedTokens < ActiveRecord::Migration[7.1]
45
+ def change
46
+ create_table :allowed_tokens do |t|
47
+ t.string :jti, null: false
48
+ t.string :aud
49
+ t.datetime :exp, null: false
50
+
51
+ t.timestamps
52
+
53
+ t.references :custom_user, foreign_key: { on_delete: :cascade }, null: false
54
+
55
+ t.index %i[jti aud]
56
+ end
57
+ end
58
+ end
59
+ ```
@@ -0,0 +1,33 @@
1
+ # Custom retrieve resource
2
+
3
+ Sometimes, your logic to retrieve user is too complex. It is not simple `User.find_by(email: identifier)`.
4
+
5
+ ```rb
6
+ # frozen_string_literal: true
7
+
8
+ Rails.application.config.to_prepare do
9
+ AuthRails.configure do |config|
10
+ config.resource_class = User
11
+ config.identifier_name = :username
12
+ config.dig_params = ->(params) { params[:identifier] }
13
+
14
+ config.retrieve_resource = lambda { |identifier|
15
+ User.where(email: identifier)
16
+ .or(User.where(username: identifier))
17
+ .first
18
+ }
19
+ end
20
+ end
21
+ ```
22
+
23
+ ## config.identifier_name
24
+
25
+ This will be used to set to `sub` of JWT's `payload`.
26
+
27
+ ## config.dig_params
28
+
29
+ To extract `identifier` for the `retrieve_resource` config.
30
+
31
+ ## config.retrieve_resource
32
+
33
+ This is where you define how to get your resource to do the sign in.
@@ -0,0 +1,16 @@
1
+ # Custom your own identifier for your model
2
+
3
+ My model does not use `email` as its `identifier`, how to make AuthRails works with my model?
4
+
5
+ Tell AuthRails what it is.
6
+
7
+ ```rb
8
+ # frozen_string_literal: true
9
+
10
+ Rails.application.config.to_prepare do
11
+ AuthRails.configure do |config|
12
+ config.resource_class = User
13
+ config.identifier_name = :username
14
+ end
15
+ end
16
+ ```
@@ -0,0 +1,14 @@
1
+ # Use your own validation password
2
+
3
+ The method `authenticate` of `has_secure_password` is not good for you? Then make your own validation password.
4
+
5
+ ```rb
6
+ # frozen_string_literal: true
7
+
8
+ Rails.application.config.to_prepare do
9
+ AuthRails.configure do |config|
10
+ config.resource_class = User
11
+ config.authenticate = ->(resource, password) { resource.password == password }
12
+ end
13
+ end
14
+ ```
@@ -0,0 +1,63 @@
1
+ # Response your own data structure
2
+
3
+ Not happy with the default response? Do not worry, you can override it easily.
4
+
5
+ ## Sign In Response
6
+
7
+ To custom your sign in response data structure, you should override the method `respond_to_create`.
8
+
9
+ ```rb
10
+ # frozen_string_literal: true
11
+
12
+ module Api
13
+ class AuthController < AuthRails::Api::AuthController
14
+ private
15
+
16
+ def respond_to_create(data)
17
+ render json: {
18
+ profile: CurrentAuth.user,
19
+ tokens: {
20
+ auth_token: data[:access_token],
21
+ refresh_token: data[:refresh_token]
22
+ }
23
+ }
24
+ end
25
+ end
26
+ end
27
+ ```
28
+
29
+ If your response for refresh token action is the same, make this as its alias.
30
+
31
+ ```rb
32
+ alias respond_to_refresh respond_to_create
33
+ ```
34
+
35
+ ## Refresh Response
36
+
37
+ To custom your refresh action response data structure, you should override the method `respond_to_refresh`.
38
+
39
+ ```rb
40
+ # frozen_string_literal: true
41
+
42
+ module Api
43
+ class AuthController < AuthRails::Api::AuthController
44
+ private
45
+
46
+ def respond_to_refresh(data)
47
+ render json: {
48
+ profile: CurrentAuth.user,
49
+ tokens: {
50
+ auth_token: data[:access_token],
51
+ refresh_token: data[:refresh_token]
52
+ }
53
+ }
54
+ end
55
+ end
56
+ end
57
+ ```
58
+
59
+ In case your sign in action's response is the same, make this as its alias.
60
+
61
+ ```rb
62
+ alias respond_to_create respond_to_refresh
63
+ ```
@@ -0,0 +1,33 @@
1
+ # Use your own strategy
2
+
3
+ AuthRails provides a base strategy that has two methods: `retrieve_resource` and `gen_token`.
4
+
5
+ In your project, you may need to handle `refresh_token` in another approach instead of `allowed_token`. You can inherit base strategy and override those two methods to do your own strategy.
6
+
7
+ ```rb
8
+ # frozen_string_literal: true
9
+
10
+ class YourOwnStrategy < AuthRails::Strategies::BaseStrategy
11
+ class << self
12
+ def retrieve_resource(payload:)
13
+ # handle payload and retrieve the user using that payload
14
+ end
15
+
16
+ def gen_token(resource:, payload:, exp: nil, secret_key: nil, algorithm: nil, jti: nil)
17
+ # handle how to generate refresh_token
18
+ end
19
+ end
20
+ end
21
+ ```
22
+
23
+ Next, add this class to the configuration.
24
+
25
+ ```rb
26
+ # frozen_string_literal: true
27
+
28
+ AuthRails.configure do |config|
29
+ config.jwt do |jwt|
30
+ jwt.strategy = YourOwnStrategy
31
+ end
32
+ end
33
+ ```
data/docs/src/index.md ADDED
@@ -0,0 +1,14 @@
1
+ ---
2
+ layout: home
3
+
4
+ hero:
5
+ name: AuthRails
6
+ tagline: Simple authentication for Rails
7
+ actions:
8
+ - theme: brand
9
+ text: Get Started
10
+ link: /introduction/getting-started
11
+ - theme: alt
12
+ text: View on GitHub
13
+ link: https://github.com/zgid123/auth_rails
14
+ ---
@@ -0,0 +1,111 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ```rb
6
+ gem 'auth_rails'
7
+ ```
8
+
9
+ ## Configuration
10
+
11
+ AuthRails provides a rake task to generate a configuration file.
12
+
13
+ ```sh
14
+ rails g auth_rails
15
+ ```
16
+
17
+ It will create a file `config/initializers/auth_rails.rb` with a default configuration.
18
+
19
+ ```rb
20
+ # frozen_string_literal: true
21
+
22
+ AuthRails.configure do |config|
23
+ config.jwt do |jwt|
24
+ jwt.access_token do |access_token|
25
+ access_token.exp = 1.hour.since
26
+ access_token.secret_key = ENV.fetch('JWT_SECRET', '')
27
+ end
28
+
29
+ # jwt.strategy = AuthRails::Strategies::AllowedTokenStrategy
30
+
31
+ # if you wanna use refresh token
32
+ # uncomment those lines below
33
+ # jwt.refresh_token do |refresh_token|
34
+ # refresh_token.http_only = true
35
+ # refresh_token.exp = 1.year.since
36
+ # refresh_token.algorithm = 'HS256'
37
+ # refresh_token.cookie_key = :ref_tok
38
+ # refresh_token.secret_key = ENV.fetch('JWT_SECRET', '')
39
+ # end
40
+ end
41
+ end
42
+
43
+ Rails.application.config.to_prepare do
44
+ AuthRails.configure do |config|
45
+ config.resource_class = User
46
+
47
+ # if you wanna use custom error classes
48
+ # uncomment code below
49
+ # config.error_class = AuthError
50
+ end
51
+ end
52
+ ```
53
+
54
+ > [!NOTE]
55
+ > [Check here](/api-reference) to see full API.
56
+
57
+ ### access_token.exp
58
+
59
+ Expires time for `access_token`.
60
+
61
+ ### access_token.secret_key
62
+
63
+ Secret key for JWT when creating `access_token`.
64
+
65
+ ### config.resource_class
66
+
67
+ User model in your application. Usually is `User`.
68
+
69
+ ## Modify User model
70
+
71
+ AuthRails will use method `authenticate` from `has_secure_password` as default.
72
+
73
+ ```rb
74
+ # app/models/user.rb
75
+ class User < ApplicationRecord
76
+ has_secure_password
77
+ end
78
+ ```
79
+
80
+ ## Use AuthRails' default controller
81
+
82
+ Define a route for sign in controller.
83
+
84
+ ```rb
85
+ # frozen_string_literal: true
86
+
87
+ Rails.application.routes.draw do
88
+ namespace :api do
89
+ resource :auth, path: 'auth', controller: 'auth', only: %i[create] do
90
+ collection do
91
+ get :refresh
92
+ end
93
+ end
94
+ end
95
+ end
96
+ ```
97
+
98
+ Create a controller that is inherited from default controller.
99
+
100
+ ```rb
101
+ # frozen_string_literal: true
102
+
103
+ module Api
104
+ class AuthController < AuthRails::Api::AuthController
105
+ end
106
+ end
107
+ ```
108
+
109
+ Now you can sign in using `POST: /api/auth` and refresh the token using `GET: /api/auth/refresh`.
110
+
111
+ Access current user as anytime using `CurrentAuth.user`.
@@ -0,0 +1,21 @@
1
+ # Introduction
2
+
3
+ AuthRails provides a simple authentication API for Ruby on Rails application.
4
+
5
+ ## Features
6
+
7
+ ### API Controller to Sign In User
8
+
9
+ AuthRails provides a default controller to do sign in for your user using JWT.
10
+
11
+ This controller will generate `access_token` to verify user for their next access and `refresh_token` to re-generate `access_token` when it is expired.
12
+
13
+ ### Allowed Token Strategy
14
+
15
+ Allowed Token Strategy will store the `refresh_token` to the database. Only those valid tokens can be used to re-generate `access_token`.
16
+
17
+ Whenever the `refresh_token` is used, it will be deleted and new `refresh_token` is stored in the database.
18
+
19
+ ## Alternative
20
+
21
+ - [devise](https://github.com/heartcombo/devise)
@@ -14,6 +14,10 @@ module AuthRails
14
14
  @resource_class ||= Config.resource_class
15
15
  end
16
16
 
17
+ def identifier_name
18
+ @identifier_name ||= Config.identifier_name&.to_sym || :email
19
+ end
20
+
17
21
  def error_class
18
22
  @error_class ||= Config.error_class || Error
19
23
  end
@@ -21,5 +25,45 @@ module AuthRails
21
25
  def jwt_strategy
22
26
  @jwt_strategy ||= Configuration::Jwt.strategy || Strategies::BaseStrategy
23
27
  end
28
+
29
+ def authenticate(resource:, password:)
30
+ if Config.authenticate.present?
31
+ raise_if_not_proc(Config.authenticate, 'Config.authenticate')
32
+
33
+ Config.authenticate.call(resource, password)
34
+ else
35
+ raise 'Don\'t know how to authenticate resource with password' unless resource.respond_to?(:authenticate)
36
+
37
+ resource.authenticate(password)
38
+ end
39
+ end
40
+
41
+ def dig_params(params:)
42
+ if Config.dig_params.present?
43
+ raise_if_not_proc(Config.dig_params, 'Config.dig_params')
44
+
45
+ Config.dig_params.call(params)
46
+ else
47
+ params[AuthRails.identifier_name]
48
+ end
49
+ end
50
+
51
+ def retrieve_resource(params:)
52
+ identifier = dig_params(params: params)
53
+
54
+ if Config.retrieve_resource.present?
55
+ raise_if_not_proc(Config.retrieve_resource, 'Config.retrieve_resource')
56
+
57
+ return Config.retrieve_resource.call(identifier)
58
+ end
59
+
60
+ AuthRails.resource_class.find_by(AuthRails.identifier_name => identifier)
61
+ end
62
+
63
+ private
64
+
65
+ def raise_if_not_proc(source, name)
66
+ raise "#{name} must be a Proc" unless source.is_a?(Proc)
67
+ end
24
68
  end
25
69
  end
@@ -3,8 +3,12 @@
3
3
  module AuthRails
4
4
  class Config
5
5
  class << self
6
- attr_accessor :error_class,
7
- :resource_class
6
+ attr_accessor :dig_params,
7
+ :error_class,
8
+ :authenticate,
9
+ :resource_class,
10
+ :identifier_name,
11
+ :retrieve_resource
8
12
 
9
13
  def jwt
10
14
  yield Configuration::Jwt
@@ -11,7 +11,7 @@ module AuthRails
11
11
  .joins(:allowed_tokens)
12
12
  .where(allowed_tokens: symbolized_payload.slice(:jti, :aud))
13
13
  .where('allowed_tokens.exp > ?', Time.current)
14
- .find_by(email: symbolized_payload[:sub])
14
+ .find_by(AuthRails.identifier_name => symbolized_payload[:sub])
15
15
  end
16
16
 
17
17
  def gen_token(resource:, payload:, exp: nil, secret_key: nil, algorithm: nil)
@@ -8,7 +8,7 @@ module AuthRails
8
8
  symbolized_payload = payload.symbolize_keys
9
9
 
10
10
  AuthRails.resource_class
11
- .find_by(email: symbolized_payload[:sub])
11
+ .find_by(AuthRails.identifier_name => symbolized_payload[:sub])
12
12
  end
13
13
 
14
14
  def gen_token(payload:, exp: nil, secret_key: nil, algorithm: nil, jti: nil, **)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AuthRails
4
- VERSION = '1.0.2'
4
+ VERSION = '1.1.1'
5
5
  end
data/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "auth_rails",
3
+ "version": "1.0.0",
4
+ "author": "Alpha",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "docs:dev": "vitepress dev docs",
8
+ "docs:build": "vitepress build docs",
9
+ "docs:preview": "vitepress preview docs"
10
+ },
11
+ "devDependencies": {
12
+ "@biomejs/biome": "^1.5.3",
13
+ "typescript": "^5.3.3",
14
+ "vitepress": "1.0.0-rc.40"
15
+ }
16
+ }