auth_rails 1.0.2 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }