You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/guides/2-essential/11_authentication_devise.md
+42-13Lines changed: 42 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -136,7 +136,7 @@ class Admin::App < Matestack::Ui::App
136
136
137
137
deflogout_action_config
138
138
{
139
-
method::delete,
139
+
method::get,
140
140
path: destroy_admin_session_path,
141
141
success: {
142
142
redirect: {
@@ -180,7 +180,7 @@ While it's similar to the `Demo::App`, the `Admin::App` does have some differenc
180
180
if admin_signed_in?
181
181
```
182
182
183
-
There is also a logout button, using an `action`compoent.
183
+
There is also a logout button, using an `action`component.
184
184
185
185
We could now use the `Admin::App` as layout, but we need to set it with `matestack_app` in the corresponding controller and we need to include our new registry with `include Admin::Component::Registry`.
186
186
@@ -581,7 +581,7 @@ class Admin::PersonsController < Admin::BaseController
581
581
end
582
582
```
583
583
584
-
We added all required actions according to our routes. Did you notice our controller now inherits from a `Admin::BaseController`? We need to create it in the next step, but why did we create one? Because all routes and corresponding actions belonging to the admin should not be visible without logging in as admin. Therefore we implement a `before_action` hook which calls devise `authenticate_admin!` helper, making sure that every action can only be called by a logged in admin. We could do this in our persons controller, but as we might add other controllers later they only need to inherit from our base controller and are also protected. Let's create the base controller in `app/controllers/admin/base_controller.rb`.
584
+
We added all required actions according to our routes. Did you notice our controller now inherits from `Admin::BaseController`? We need to create it in the next step, but why did we create one? Because all routes and corresponding actions belonging to the admin should not be visible without logging in as admin. Therefore we implement a `before_action` hook which calls devise `authenticate_admin!` helper, making sure that every action can only be called by a logged in admin. We could do this in our persons controller, but as we might add other controllers later they only need to inherit from our base controller and are also protected. Let's create the base controller in `app/controllers/admin/base_controller.rb`.
@@ -592,8 +592,9 @@ class Admin::BaseController < ApplicationController
592
592
end
593
593
```
594
594
595
-
In order for devise to use our sign in page, we need to create a custom session controller. Also we need to override the create and delete action of devise session controller, because we would else get errors or unwanted behavior. If we don't override the `create` action devise will trigger a full website reload and rerendering our sign in page, which means we couldn't handle login errors dynamically. The `delete` action needs to be overriden because devise usual redirect will not work with matestack. See below on how to create the session controller for devise and don't forget to update the routes in order to tell devise to use the correct controller.
595
+
In order for devise to use our sign in page, we need to create a custom session controller. We also specify a path admins should be redirected to after sign out.
596
596
597
+
See below on how to create the session controller for devise.
597
598
598
599
`app/controllers/admin/sessions_controller.rb`
599
600
```ruby
@@ -607,21 +608,45 @@ class Admin::SessionsController < Devise::SessionsController
And tell devise to use it by requiring it in the initializer and updating the config. We also need to update the `sign_out_via` configuration parameter to use http GET requests for sign out instead of DELETE.
Finally remember to update the routes in order to tell devise to use the correct controller.
649
+
625
650
`config/routes.rb`
626
651
```ruby
627
652
Rails.application.routes.draw do
@@ -644,6 +669,8 @@ Rails.application.routes.draw do
644
669
end
645
670
```
646
671
672
+
If you want more information on why these changes and configurations are necessary take a look at our [devise guide](/docs/guides/5-authorization_authentication/devise.md).
673
+
647
674
But if you try to start your application locally, visiting the admin pages doesn't work yet - what's going on?
648
675
649
676
### Styling, Layout & JavaScript
@@ -853,10 +880,12 @@ git commit -m "Add admin login to DemoApp, add default :role to Person model, re
853
880
854
881
What exactly is going on under the hood with all the admin sign in stuff, you may wonder?
855
882
856
-
Here's a quick overview: Instead over implementing loads of (complex) functionality with a load of implications and edge cases, we use the `Devise` gem for a rock-solid authentication. It takes care of hashing, salting and storing the password, and through the `Devise::SessionsController`, of managing the session cookie. All that's left for us to do is check for the existence of said cookie by using the `authenticate_admin!` helper. If the required cookie is not present, the controller responds with an error code.
883
+
Here's a quick overview: Instead of implementing loads of (complex) functionality with a load of implications and edge cases, we use the `Devise` gem for a rock-solid authentication. It takes care of hashing, salting and storing the password and managing session cookies. All that's left for us to do is check for authentication of admins by using the `authenticate_admin!` helper.
857
884
858
885
`Devise` could do a lot more, but as this is a basic guide, we will leave it with that. For even more fine-grained control over access rights (authorization) within your application (e.g. by introducing a superadmin or having regional and national manager roles), we recommend to take a look at two other popular Ruby/Rails gems, [Pundit](https://github.com/varvet/pundit) and [CanCanCan](https://github.com/CanCanCommunity/cancancan).
859
886
887
+
If you want to know more about using devise with matestack, checkout our [devise guide](/docs/guides/5-authorization_authentication/devise.md).
888
+
860
889
## Creating a admin
861
890
862
891
In order to use the admin app we need to create an admin with credentials which we can now use to sign in.
Generating a devise model or updating an existing one works as usual. Just use devise generator to create a model. If you already have a model which you want to extend for use with devise take a look at the devise documentation on how to do so.
25
33
@@ -30,7 +38,7 @@ rails generate devise user
30
38
31
39
And ensure `devise_for :user` is added to the `app/config/routes.rb`
32
40
33
-
## Devise Helpers
41
+
## Devise helpers
34
42
35
43
Again nothing unusual here. We can access devise helper methods inside our controllers, apps, pages and components like we would normally do. In case of our user model this means we could access `current_user` or `user_signed_in?` in apps, pages and components.
36
44
@@ -59,26 +67,26 @@ class ExampleController < ApplicationController
59
67
end
60
68
```
61
69
62
-
## Devise Login
70
+
## Devise sign in
63
71
64
-
Using the default devise login views should work without a problem, but they will not be integrated inside a matestack app. Let's assume we have a profile matestack app called `Profile::App`. If we want to take advantage of matestacks transitions features (not reloading our app layout between page transitions) we can not use devise views, because we would need to redirect to them and therefore need to reload the whole page. Requiring us for example to implement our navigation twice. In our `Profile::App` and also in the our devise sign in view.
72
+
Using the default devise sign in views should work without a problem, but they will not be integrated with a matestack app. Let's assume we have a profile matestack app called `Profile::App`. If we want to take advantage of matestacks transitions features (not reloading our app layout between page transitions) we can not use devise views, because we would need to redirect to them and therefore need to reload the whole page. Requiring us for example to implement our navigation twice. In our `Profile::App` and also in our devise sign in view.
65
73
66
-
Therefore we need to adjust a few things and create some pages. First we need a custom sign in page containing a form with email and password inputs.
74
+
Therefore we need to adjust a few things and create some pages. First we create a custom sign in page containing a form with email and password inputs.
@@ -95,16 +103,18 @@ class Profile::Pages::Sessions::SignIn < Matestack::Ui::Page
95
103
}
96
104
},
97
105
failure: {
98
-
emit:'login_failure'
106
+
emit:'sign_in_failure'
99
107
}
100
108
end
101
109
102
110
end
103
111
```
104
112
105
-
This page displays a form with a email and password input. The default required parameters for a devise login. It also contains a `toggle` component which gets shown when the event `login_failure` is emitted. This event gets emitted in case our form submit was unsuccessful as we specified it in our `form_config` hash. If the form is successful our app will make a transition to the page the server would redirect to.
113
+
This page displays a form with an email and password input. The default required parameters for a devise sign in. It also contains a `toggle` component which gets shown when the event `sign_in_failure` is emitted. This event gets emitted in case our form submit was unsuccessful as we specified it in our `form_config` hash. If the form is successful our app will make a transition to the page the server would redirect to.
114
+
115
+
Remember to use `redirect` instead of `transition`, if you have conditional content depending on a logged in user inside your app. You have to use `redirect` because the app needs to be reloaded, which happens only with `redirect`.
106
116
107
-
In order to render our sign in page when someone tries to access a route which needs authentication or someone visits the sign in page we must override devise session controller in order to render this page. We do this by configuring our routes to use a custom controller.
117
+
In order to render our sign in page when someone tries to access a route which needs authentication or visits the sign in page we must override devise session controller in order to render our page. We do this by configuring our routes to use a custom controller.
108
118
109
119
`app/config/routes.rb`
110
120
```ruby
@@ -117,7 +127,7 @@ Rails.application.routes.draw do
117
127
end
118
128
```
119
129
120
-
Override the `new` action in order to render our sign in page.
130
+
Override the `new` action in order to render our sign in page and set the correct matestack app in the controller. Also remember to include the components registry. This is necessary if you use custom components in your app or page, because without it matestack can't resolve them.
121
131
122
132
`app/controllers/users/sessions_controller.rb`
123
133
```ruby
@@ -135,43 +145,70 @@ class Users::SessionController < Devise::SessionController
135
145
end
136
146
```
137
147
138
-
Finally we need to override the create method in order to fully leverage matestacks potential. Matestack expects to retrieve a json response with a html error code if the sign in has failed due to matestacks form error handling. To achieve this we need to override the `create` method as you can see below:
148
+
Now our sign in is nearly complete. Logging in with correct credentials works fine, but logging in with incorrect credentials triggers a page reload and doesn't show our error message.
149
+
150
+
Devise usually responds with a 401 for wrong credentials but intercepts this response and redirects to the new action. This means our `form` component recieves the response of the `new` action, which would have a success status. Therefore it redirects you resulting in a rerendering of the sign in page. So our `form` component needs to recieve a error code in order to work as expected. To achieve this we need to provide a custom failure app.
151
+
152
+
Create the custom failure app under `lib/devise/json_failure_app.rb` containing following code:
We stayed as close to devise implementation as possible. The important part is line 3 where we return a json response with error code 401 if warden couldn't authenticate the resource.
166
+
We only want to overwrite the behavior of the failure app for request with `application/json` as content type, setting the status to a 401 unauthorized error and the content_type to json.
150
167
151
-
**Wrap Up**
152
-
That's it. Now you have a working fully integrated login with devise and matestack. All we needed to do was to create a sign in page, update our routes to use a custom session controller and override two methods in this controller.
168
+
There is only one thing left, telling devise to use our custom failure app. Therefore add/update the following lines in `config/initializers/devise.rb`.
That's it. When we now try to sign in with incorrect credentials the `form` component triggers the `sign_in_failure` event, which sets off our `toggle` component resulting in displaying the error message.
160
179
180
+
**Wrap Up**
181
+
That's it. Now you have a working sign in with devise fully integrated into matestack. All we needed to do was creating a sign in page, updating our routes to use a custom session controller, overriding the new action, creating a custom failure app and updating the devise config.
161
182
162
183
163
-
## Example
184
+
## Devise sign out
164
185
165
-
This is just your average Rails user controller. The `before_action` gets called on initial pageload and on every subsequent AJAX request the client sends.
186
+
Creating a sign out button in matestack is very straight forward. We use matestacks [`action` component](/docs/api/2-components/action.md) to create a sign out button. See the example below:
Notice the `method: :get` in the configuration hash. We use a http GET request to sign out, because the browser will follow the redirect send from devise session controller and then matestack tries to load the page where we have been redirected to. When we would use a DELETE request the action we would be redirected to from the browser will be also requested with a http DELETE request, which will result in a rails routing error. Therefore we use GET and need to configure devise accordingly by changing the `sign_out_via` configuration parameter.
175
208
176
-
end
209
+
```ruby
210
+
# The default HTTP method used to sign out a resource. Default is :delete.
0 commit comments