Site blog title mreza

IMPLEMENTING GOOGLE AUTHENTICATOR IN ACTIVE ADMIN

31/10/2018
Aa 2fa

In this blog post we will be showing you how to implement a two-factor authentication solution for Rails' Active Admin by using Google Authenticator in combination with a custom mailer for sending the QR registration code. A couple of other similar example might already exist elsewhere on the internet but information is fairly sparse when it comes to Active Admin specifically. Like we already stated, we'll be make use of Google Authenticator so you'll have to install the appropriate Ruby gem by adding "google-authenticator-rails" to your project's Gemfile. Let's get started!

   First of all, let's talk a little bit about the very concept of two factor authentication. You might have already come across two factor authentication (2FA) under a different name elsewhere - multi-factor authentication (MFA). 2FA or MFA is mostly used on login screens as a way of verifying and double-checking a user's identity for additional security. Rather than depending solely on a user's email address and their password, 2FA attempts to decrease the risk of imposters acquiring access to personal data by properly establishing a user's identity first. This is done by using an additional piece of information to verify the identity, e.g. in this case a code that only the person knows. Nowadays there's more and more cases where hackers have easy access to private data and this is where 2FA comes into play. Because there's lots of ways to perform 2FA it means there's also lots of different implementations, some use email, others use SMS, etc. One of the implementations is Google Authenticator and that's what we'll be using in our project.

   Before delving deeper into this topic I would like to tell you more about what you'll be getting with this gem so that it's easier for you to follow the setup steps. First you'll add the gem to your project. After that you will need to set up the environment for the gem to work as it should. This is done by adding two columns to the model that you'll be using for implementing Google Authenticator (in this case 'AdminUser'). The first column is 'google_secret'. This is where we'll be storing the encrypted code (which can be reset) use to connect the model (in this case AdminUser) with Google Authenticator. The code stored in this column will enable our AdminUser to register via the Google Authenticator mobile app and it can also be used as a QR code so the user can scan it with their smartphone.

   Next up is the column called "mfasecret". This is where we'll be storing a 6 digit code each time the user tries to log into their account. The user cannot get this code until they register with a Google Secret Code on the mobile application. Every time the user logs out, the mfasecret is reset to nil. This isn't the only way to do it, though, as you can change this behavior of the Google Authenticator gem. Let's move on.

After you've added these two columns to your model, you'll also add 'actsasgoogle_authenticated' to the same model (the one you want to be checked against Google Authenticator). This lets Google Authenticator hook into the model by injecting all the additional functionality into it so 2FA can work as intended.

   The next step is to add a session object. We'll call it "AdminUserMfaSession". We need a session object because we're going to temporarily store the existing model (AdminUser) with their 'mfasecret' (the 6 digit code generated by Google Authenticator) after logging in. This will be done only if the mfasecret is correct. To be able to check whether the code they entered is valid, we'll need to also add some functionality inside of a controller. We'll create a controller called 'AdminUserMfaSessionController'. Its only assignment is to check whether the entered 'mfasecret' is correct. If the entered mfasecret is correct, the controller will store the AdminUser and their mfasecret inside the session object we previously created ('AdminUserMfaSession'). If the 'mfasecret' is not correct, then we'll be returning a warning message to the user that the info they've entered is invalid. To wrap this up and make it all work, we'll also have to add a method that's checked every time AdminUser makes a change of routes on the site and/or refreshes the current route. We can do this by adding a 'before' filter to config/initializers/activeadmin.rb(we could also add this before filter in Application controller but now it is only oriented on Active admin). The method will be called on each request under Active admin and it'll check if the data stored in our session object ('AdminUsermfaSesstion') is valid and whether they're allowed to access the site. They'll only be allowed access if 'mfasecret' is correct. If the 'mfasecret' is not correct, then before filter will redirect the user (in our case, AdminUser) to a controller view where they can input a valid 'mfasecret'. That's the general overview of what the gem does, what its workflow is and what the process will appear like from a user's perspective. Let's head to the actual implementation! (I'll provide code snippets to help you along)



1. Add 'google-authenticator-rails' gem to your project.



The first way to do so is by adding the following line:


gem 'google-authenticator-rails'

to your Gemfile and save.

Then go to terminal and run ‘bundle install’.

The second way of adding our gem is by running the following directly from your terminal.

The command to execute:


install google-authenticator-rails



2. Add required columns to your model



Now we need to add the columns used for storing the 'googlesecret' and 'mfasecret' to our model.

If you need help on how to generate a migration, follow these steps:

  • Go to your Terminal and head to the root of your project
  • Run 'rails generate migration AddGoogleSecretToAdminUser'
  • Find generated migration and type in the following:

class AddGoogleSecretToAdminUser < ActiveRecord::Migration
  def change
    add_column :admin_users, :google_secret, :string
    add_column :admin_users, :mfa_secret, :string
  end
end

  • Save your changes and go back to the terminal
  • run 'rake db:migrate'



3. Connect Google authenticator with your model (AdminUser)



To achieve this you'll be adding 'actsasgoogleauthenticated' to your model (AdminUser). I encountered some problems during this step. There are a couple of settings you will need to set up. We'll have to encrypt the Google token saved in our 'googlesecret'. As the documentation of this gem is not that good, if you encounter this problem for the first time do not fret! So, looking up the readme file for this gem I found out that you need to add 'encryptsecrets => true'. That setting will encrypt 'googlesecret' inside of the database.

This is how it'll look like:


class AdminUser < ActiveRecord::Base
 acts_as_google_authenticated :lookup_token => :mfa_secret, :encrypt_secrets => true
End



4. Create session object to be able to authenticate



Now you need to create an empty model to store AdminUser data for authentication inside of it.

Go to app/models and add a file named adminusermfa_session.rb

The content of this model should look like this:


class AdminUserMfaSession <  GoogleAuthenticatorRails::Session::Base
end



5. Create the controller



We're now at one of the most important steps of this entire process. We'll need to create a controller to check if the 6 digit code we entered and stored into our 'mfa_secret' column is indeed correct. Depending on that, the controller will either save the the correct session data into our session object (AdminUserMfaSession) or alternatively prevent the user from accessing the resources they do not have access to until they've established their identity. Our controller will have two methods, these are "new" and "create". The "new" method is the one that'll load your view after you've finished authorization with email (username) and password. Inside of this view, the user will need to input their 6 digit code which was created by Google Authenticator. The "create" method is the actual backbone of it all and is where we'll check if the code is valid. If it's not valid, the view will redirect the user and prevent access until they verify their identity with a correct code. During that time, the user (in our case, AdminUser) will not have access to other parts of the site - only after entering a valid code will they be able to continue accessing other resources.

Here is the code we ended up with:


class AdminUserMfaSessionController < ApplicationController
  def new
    @skip_header_and_footer = true
    @current_admin_user = current_admin_user
    current_admin_user.google_secret_value if current_admin_user.set_google_secret
  end

   def create
    @skip_header_and_footer = true
    admin_user = current_admin_user 
    admin_user.salt = params[:create_admin_user_mfa_session_path][:mfa_code]
    admin_user.save!
     if admin_user.google_authentic?(params[:create_admin_user_mfa_session_path][:mfa_code])
      AdminUserMfaSession.create(admin_user)
      redirect_to '/admin'
    else
      flash[:error] = "Wrong code"
      render :new
    end
  end
 end

We'll fill in the gaps later on, if anything's unclear in the above code snippet. All will be explained at the end after the environment is fully set up.

Let's now move on to modifying our ApplicationController, our next step in integrating Google Authenticator.



6. Setting up before filter in active admin



We'll need to add a new "before" filter to config/initializers/activeadmin.rb. The filter will be run on each request under active admin. Like we already stated, the user's access will be dependent on the 6 digit code we implemented earlier. The user (AdminUser) will enter the code, a new AdminUserMfaSession object will be created with the AdminUser's data and if the session exists and is valid - before filter will be skipped. If no AdminUserMfaSession exists, the AdminUser will be redirected to the view in which they can enter the 6 digit code. Another thing worth pointing out - when the user logs out, we have to destroy the data inside of AdminUserMfaSession. The 'mfasecret' column in AdminUser will also need to be set to nil. Reason being that our implementation requires the user to enter the 6 digit code on each login attempt. If your case differs, you might want to change this.
Now, here is our code and it should be placed inside config/initializers/active_admin.rb


ActiveAdmin.setup do |config|
config.before_filter do
    return if Rails.env.test? || Rails.env.development?
    if !(admin_user_mfa_session = AdminUserMfaSession.find) && (admin_user_mfa_session ? admin_user_mfa_session.record == current_admin_user : !admin_user_mfa_session)
      redirect_to new_admin_user_mfa_session_path
    end if current_admin_user && "/admin/mfa" != request.path
    if "/admin/logout" == request.path
      current_admin_user.salt = nil
      current_admin_user.save!
      AdminUserMfaSession.destroy
    end
  end
end

Here's an explanation of some code that might be confusing.

Firstly,


end if current_admin_user && ......

We don't want this method to check Google Authentication if there is no AdminUser logged in. before should be checked and 6 digit key should be requested only after AdminUser has tried to login with their email and password so Google Authenticator could connect the account ( logic! :') ).

Secondly,


end if .... && "/admin/mfa" != request.path

This part of code solved my problem… I had issues with multiple requests and my path kept changing multiple times during request. Which caused an error. To solve that error I had to check for the user's current path. If the current path was “admin/mfa“ - don't redirect, in short. Maybe this will be of help to you, so I decided to keep it in this tutorial just in case you encounter a similar issue.

Next I will go on about the actual view for inputting the code generated by our Google Authenticator as well as the routes we have to add for the gem to work properly. I had some issues with this part while implementing this and after fixing them I decided to write it up - they're the main reason why you're reading this!

Let's talk about the view. Like I said, after the AdminUser fills in their correct credentials (email and password), they are not yet able to access the site and the data (in our case, ActiveAdmin). They still need to go through a second confirmation step to verify their identity. For that reason, the AdminUser will be redirected to the view where they get asked to enter a 6 digit code from Google Authenticator.

Here is relevant code:


<div class="mfa-container"> 

  <%= form_for :create_admin_user_mfa_session_path do |f| %>    

    <p>Google Authenticator code:</p>   

    <%= f.text_field :mfa_code, :maxlength => 6%>   

    <%= f.submit "Log In" %>    

  <%end%>   
</div>  

You should place it depending on your own controller and method naming scheme - we'll be putting our view in the following place:

app/views/adminusermfa_session/new.html.erb

So, as you can see, when the AdminUser tries to login, they get redirected by ApplicationController to “newadminusermfasessionpath“. This path leads to “adminusermfasessioncontroller“ and more specifically, its method “new“. As you can see our view is placed inside of app/views/adminusermfasession/new.html.erb which is the reason why the naming scheme is such. Your mileage may vary. Under the view “new.html.erb “ we have an input field for entering the 6 digit code generated by Google Authenticator on your mobile device.

screen shot 2018-10-29 at 09 46 52

After the AdminUser enters their code, they need to click on the “Log In“ button. On click, “:mfacode“ containing the 6 digit code entered by the AdminUser gets sent through to the “createadminusermfasessionpath“ path which ends up in the "create" method inside of our "adminusermfasessioncontroller“ file.

One more tip about “googlesecret“. You should send the “googlesecret“ at least once to AdminUser via email so only they can see it. You can do so either by sending a QR code or a key. The method for creating the QR code is “currentadminuser.googleqruri“ and for the key it is “currentadminuser.googlesecretvalue“.



7. Using Google Authenticator



Now I'd like to show you how Google Authenticator actually works in practice.

  1. The AdminUser must have a “googlesecret“ value set in the database. Reason being that “googlesecret“ contains the encrypted key connecting the AdminUser to their Google Authenticator. This key allows the AdminUser to connect their profile with the mobile Google Authenticator application. This code can be displayed either as a regular key or as a scannable QR code.

  2. Now after our AdminUser has a “googlesecret“ created inside the database, they need to connect their mobile application with their account so it can serve as a verification of identity in the future.



    They can do so by either scanning the QR code (“current
    adminuser.googleqruri “) or by typing in the key manually (“currentadminuser.googlesecret_value“).

  3. After our AdminUser connects their account to the mobile app they should get to the view rendering their own 6 digit code.The above code is what the AdminUser enters on Log In



  4. Now our AdminUser is ready for using the Google secret. The procedure for authentication is now fairly straightforward.

  • First the AdminUser needs to do a standard authentication with their credentials (email/username and password).

    screen shot 2018-10-31 at 10 05 40

  • Then the AdminUser will be redirected to the view with the 6 digit input field.

  • They'll receive this in their Google Authenticator application and it'll look something like this.



  • Then they'll enter the code in the input fields like so,

    screen shot 2018-10-31 at 10 06 15

  • By clicking on “Log In“ they will now be able to make use of the rest of the site after passing Google Authenticator verification (or whatever resources you yourself might have been hiding from them behind 2FA).


    Thanks for tuning in and I hope this was of help to you! If you have any comments and/or question, shoot!

Category: Rails, Security
OTHER POSTS
OTHER POSTS
Created with Sketch.
Railsios
06/06/2018 / Tin Ilijaš

UPLOAD IMAGE FROM SWIFT 3 IOS APP TO RAILS 5 SERVER

Once upon a time I was given the task to send an image captured on IOS to our Rails back-end server and at first I figured: “Wow this can’t be such a big deal, I know how to take an image and I know how to make a request so why would thi...
Omniauth f1d5b77f6e5fc9adc96fdb4dc3a7d5f3 %281%29
06/06/2018 / Krešimir Bojčić

DEVISE WITH OMNIAUTH FOR SINGLE AND MULTIPLE MODELS – RAILS 5

In this post we'll describe on how to use OmniAuth in combination with Rails and Devise to support authentication of existing and new users without asking for email/password combinations.
Created with Sketch.
Conferencecam group
29/06/2018 / Krešimir Bojčić

LOGITECH GROUP – REMOTE VIDEO CONFERENCING SYSTEM REVIEW

To have a successful remote agency/client relationship you need three main ingredients: 1. Trust 2. Effective communication 3. Delivering results
Created with Sketch.
Global infra 3.30.18.b559f46825615c1ae40f319d0c4d9139fea9c492
29/06/2018 / Krešimir Bojčić

SCALE FOR SPEED AND AVAILABILITY

In this post I'll go over various options for scaling your business web platform. We'll take a look at five different approaches. There is no wrong or right approach, it is just a matter of what aspects you want to emphasize and what you...
Railsandreact
28/06/2018 / Matija Munjaković

SETTING UP REACT & RAILS HAS NEVER BEEN EASIER

Just a couple of days ago Beta 1 of Rails 5.1 got released, bringing with it a slew of new features. Most prominent among them being the inclusion of Webpack. For the uninitiated, Webpack serves as a bundling tool for your project’s Java...
Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

FREE SSL ON AWS OPSWORKS RAILS APP

In this post I'll try and describe how to set up SSL for your Rails app. This solution is free and will automatically extend the certificate once the certificate runs its course.
Created with Sketch.
23 512
06/06/2018 / Tin Ilijaš

HOW TO DEACTIVATE USER – RAILS WITH DEVISE

Sometimes when I find the time (which happens roughly 23 to 37 times each and every day) I visit Stack Overflow, hopefully to solve a problem – but regularly leave irritated and heartbroken because the solution is too geeky and doesn’t e...
7e6dcc68 1ba3 11e7 8286 407dd1a1b50f
29/06/2018 / Tin Ilijaš

SWITCH USERS WITH USERSWITCH

Recently we were hired by a startup and quickly assigned a task to build a brand new feature for them. So no problemo here! The feature was fairly extensive which necessitated thorough testing – perhaps a third of the entire application ...
Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

HOW TO BUILD AN MVP

So, you’ve decided to build a new disruptive product that will earn you glory and money? Congratulations… welcome to the club and good luck!
Created with Sketch.
1 ee4irlninahubiurfqv3fq
29/06/2018 / Tin Ilijaš

8 TIPS TO BECOME A DEVELOPER

Almost a year has flown by ever since I started learning programming from scratch. As a result of that, I am writing this blog post to help new and old developers with some tips I learned in this beautiful world of programming.
Untitled
03/07/2018 / Krešimir Bojčić

VISUALIZE DATABASE SCHEMA – POSTGRESQL DB – RAILS 5

When working on bigger systems it helps having your database model visualized. There are many great solutions but a few of them are free. If you fit in the “small enough” not to use [ER/Studio Data Architect](https://www.idera.com/er-...
Created with Sketch.
Helpprotectcompanydata large
29/06/2018 / Krešimir Bojčić

PROTECT THE DATA!

If you were to ask yourself – what is the essence of any software system – what would you say? (that is, what would you have said without having read the title of this post 😃).
Created with Sketch. Created with Sketch.
Hmawawbapi
03/07/2018 / Matija Munjaković

HANDLING MOBILE AUTHENTICATION WITH A WEB-BASED API

Quite recently I had the chance of implementing a log-in/authentication system for one of our mobile applications. Resources available for both the Android and iOS eco-systems are fairly exhaustive but comparisons of the pros and cons of...
Created with Sketch.
Railsios
06/06/2018 / Tin Ilijaš

UPLOAD IMAGE FROM SWIFT 3 IOS APP TO RAILS 5 SERVER

Once upon a time I was given the task to send an image captured on IOS to our Rails back-end server and at first I figured: “Wow this can’t be such a big deal, I know how to take an image and I kno...
Created with Sketch.
Omniauth f1d5b77f6e5fc9adc96fdb4dc3a7d5f3 %281%29
06/06/2018 / Krešimir Bojčić

DEVISE WITH OMNIAUTH FOR SINGLE AND MULTIPLE MODELS – RAILS 5

In this post we'll describe on how to use OmniAuth in combination with Rails and Devise to support authentication of existing and new users without asking for email/password combinations.
Created with Sketch.
Conferencecam group
29/06/2018 / Krešimir Bojčić

LOGITECH GROUP – REMOTE VIDEO CONFERENCING SYSTEM REVIEW

To have a successful remote agency/client relationship you need three main ingredients: 1. Trust 2. Effective communication 3. Delivering results
Created with Sketch.
Global infra 3.30.18.b559f46825615c1ae40f319d0c4d9139fea9c492
29/06/2018 / Krešimir Bojčić

SCALE FOR SPEED AND AVAILABILITY

In this post I'll go over various options for scaling your business web platform. We'll take a look at five different approaches. There is no wrong or right approach, it is just a matter of what as...
Created with Sketch.
Railsandreact
28/06/2018 / Matija Munjaković

SETTING UP REACT & RAILS HAS NEVER BEEN EASIER

Just a couple of days ago Beta 1 of Rails 5.1 got released, bringing with it a slew of new features. Most prominent among them being the inclusion of Webpack. For the uninitiated, Webpack serves as...
Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

FREE SSL ON AWS OPSWORKS RAILS APP

In this post I'll try and describe how to set up SSL for your Rails app. This solution is free and will automatically extend the certificate once the certificate runs its course.
Created with Sketch.
23 512
06/06/2018 / Tin Ilijaš

HOW TO DEACTIVATE USER – RAILS WITH DEVISE

Sometimes when I find the time (which happens roughly 23 to 37 times each and every day) I visit Stack Overflow, hopefully to solve a problem – but regularly leave irritated and heartbroken because...
Created with Sketch.
7e6dcc68 1ba3 11e7 8286 407dd1a1b50f
29/06/2018 / Tin Ilijaš

SWITCH USERS WITH USERSWITCH

Recently we were hired by a startup and quickly assigned a task to build a brand new feature for them. So no problemo here! The feature was fairly extensive which necessitated thorough testing – pe...
Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

HOW TO BUILD AN MVP

So, you’ve decided to build a new disruptive product that will earn you glory and money? Congratulations… welcome to the club and good luck!
Created with Sketch.
1 ee4irlninahubiurfqv3fq
29/06/2018 / Tin Ilijaš

8 TIPS TO BECOME A DEVELOPER

Almost a year has flown by ever since I started learning programming from scratch. As a result of that, I am writing this blog post to help new and old developers with some tips I learned in this b...
Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

VISUALIZE DATABASE SCHEMA – POSTGRESQL DB – RAILS 5

When working on bigger systems it helps having your database model visualized. There are many great solutions but a few of them are free. If you fit in the “small enough” not to use [ER/Studio D...
Created with Sketch.
Helpprotectcompanydata large
29/06/2018 / Krešimir Bojčić

PROTECT THE DATA!

If you were to ask yourself – what is the essence of any software system – what would you say? (that is, what would you have said without having read the title of this post 😃).
Created with Sketch. Created with Sketch.
Hmawawbapi
03/07/2018 / Matija Munjaković

HANDLING MOBILE AUTHENTICATION WITH A WEB-BASED API

Quite recently I had the chance of implementing a log-in/authentication system for one of our mobile applications. Resources available for both the Android and iOS eco-systems are fairly exhaustive...
Created with Sketch. Created with Sketch.
Railsios
06/06/2018 / Tin Ilijaš

UPLOAD IMAGE FROM SWIFT 3 IOS APP TO RAILS 5 SERVER

Once upon a time I was given the task to send an image captured on IOS to our Rails back-end server and at first I figured: “Wo...
Created with Sketch. Created with Sketch.
Omniauth f1d5b77f6e5fc9adc96fdb4dc3a7d5f3 %281%29
06/06/2018 / Krešimir Bojčić

DEVISE WITH OMNIAUTH FOR SINGLE AND MULTIPLE MODELS – RAILS 5

In this post we'll describe on how to use OmniAuth in combination with Rails and Devise to support authentication of existing a...
Created with Sketch. Created with Sketch.
Conferencecam group
29/06/2018 / Krešimir Bojčić

LOGITECH GROUP – REMOTE VIDEO CONFERENCING SYSTEM REVIEW

To have a successful remote agency/client relationship you need three main ingredients: 1. Trust 2. Effective communication...
Created with Sketch. Created with Sketch.
Global infra 3.30.18.b559f46825615c1ae40f319d0c4d9139fea9c492
29/06/2018 / Krešimir Bojčić

SCALE FOR SPEED AND AVAILABILITY

In this post I'll go over various options for scaling your business web platform. We'll take a look at five different approache...
Created with Sketch. Created with Sketch.
Railsandreact
28/06/2018 / Matija Munjaković

SETTING UP REACT & RAILS HAS NEVER BEEN EASIER

Just a couple of days ago Beta 1 of Rails 5.1 got released, bringing with it a slew of new features. Most prominent among them ...
Created with Sketch. Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

FREE SSL ON AWS OPSWORKS RAILS APP

In this post I'll try and describe how to set up SSL for your Rails app. This solution is free and will automatically extend th...
Created with Sketch. Created with Sketch.
23 512
06/06/2018 / Tin Ilijaš

HOW TO DEACTIVATE USER – RAILS WITH DEVISE

Sometimes when I find the time (which happens roughly 23 to 37 times each and every day) I visit Stack Overflow, hopefully to s...
Created with Sketch. Created with Sketch.
7e6dcc68 1ba3 11e7 8286 407dd1a1b50f
29/06/2018 / Tin Ilijaš

SWITCH USERS WITH USERSWITCH

Recently we were hired by a startup and quickly assigned a task to build a brand new feature for them. So no problemo here! The...
Created with Sketch. Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

HOW TO BUILD AN MVP

So, you’ve decided to build a new disruptive product that will earn you glory and money? Congratulations… welcome to the clu...
Created with Sketch. Created with Sketch.
1 ee4irlninahubiurfqv3fq
29/06/2018 / Tin Ilijaš

8 TIPS TO BECOME A DEVELOPER

Almost a year has flown by ever since I started learning programming from scratch. As a result of that, I am writing this blog ...
Created with Sketch. Created with Sketch.
Untitled
03/07/2018 / Krešimir Bojčić

VISUALIZE DATABASE SCHEMA – POSTGRESQL DB – RAILS 5

When working on bigger systems it helps having your database model visualized. There are many great solutions but a few of them...
Created with Sketch. Created with Sketch.
Helpprotectcompanydata large
29/06/2018 / Krešimir Bojčić

PROTECT THE DATA!

If you were to ask yourself – what is the essence of any software system – what would you say? (that is, what would you have sa...
Created with Sketch. Created with Sketch.
Hmawawbapi
03/07/2018 / Matija Munjaković

HANDLING MOBILE AUTHENTICATION WITH A WEB-BASED API

Quite recently I had the chance of implementing a log-in/authentication system for one of our mobile applications. Resources av...