NAV

Introduction

Welcome to the Estonian Internet Foundation's eeID documentation! This document describes the technical characteristics of the Estonian Internet Foundation eeID authentication service and includes advice for interfacing the client application with e-services. The eeID authentication service can be used by institutions and private persons to add the support of various different authentication methods to its e-service:

eeID service is based on Estonian Information System Authorities solution TARA2.

OpenID Connect

The eeID authentication service is based on the OpenID Connect protocol, which is built on top of the OAuth 2.0 authorization framework. It's designed to provide a secure and standardized way to authenticate users and obtain their basic profile information. OIDC is commonly used in applications that require user authentication, such as web and mobile applications.

Limited subset from standards was chosen and some adjustments were made. The main selections and adjustments compared to the full OpenID Connect protocol are the following:

Getting started

In order to get started you have to sign up and create your first client (service) in the eeID manager.

  1. Add a new contact first. From the main menu select Contacts to go to the contacts management view. Click on + Create New Contact and fill in the form. Create New Contact

  2. From the main menu select Services to go to the service management view. Click on + Create New Service. Create New Service

  3. Fill in the form New Service Form

  4. All the fields must be valid to proceed.

Once you submit the form, it will be reviewed by the service administrators at the Estonian Internet Foundation They will assess the details provided in your application to ensure they meet the necessary criteria and adhere to the terms of use. If your application meets all the requirements, it will be approved and you will be provided with the client ID and secret. In case there are issues or discrepancies in your application, it might be rejected. After the review process is completed, you will receive a notification regarding the status of your application. This notification will inform you whether your application has been approved or rejected.

Authentication methods

By default, the eeID service facilitates the following authentication methods:

These authentication methods stand as testimony to Estonia's advanced digital infrastructure, offering citizens secure, reliable, and convenient options for online authentication. By leveraging state-of-the-art technology and stringent security protocols, these methods ensure that Estonian citizens can safely access e-services, conduct online transactions, and sign digital documents from the comfort of their homes or on the go.

Country Authentication methods Required scopes
Belgium eIDAS eidas
Czech Republic mojeID eidas
Estonia ID-card, Mobile-ID, Smart-ID eidas idcard mid smartid
Latvia Smart-ID, eParaksts karte, eParaksts mobile eidas smartid
Lithuania Smart-ID, Mobile-ID eidas smartid mid
Portugal eIDAS eidas
Sweden eIDAS eidas

For a demo of WebAuthn, visit https://webauthn.io/.

Creating a WebAuthn Credential through eeID:

  1. Initial Authentication
    Before creating a WebAuthn credential, the users must first authenticate themselves using one of the other methods provided in the eeID service. This step ensures that the user's identity is verified through a secure and recognized authentication method.

  2. eeID as Identity Provider
    Once the initial authentication is successful, the eeID service acts as an identity provider. In this role, it verifies and stores the authenticated data, establishing a secure and trusted identity framework for the user.

  3. Creating the Webtuhn Credential
    Following the successful authentication through eeID, the user can proceed to create a WebAuthn credential. This process involves:

    • Registering a Local Authenticator. The user will register a local authenticator, such as a biometric identifier (fingerprint, facial recognition, etc.) or a security key.
    • Public Key Cryptography. The WebAuthn method leverages public key cryptography, where a private key is stored on the user's local device, and a public key is stored on the server. This setup ensures a secure and password-less authentication process.
    • Credential ID. Upon successful registration, a unique Credential ID is generated, which will be used for future authentications.
  4. Future Authentications
    With the WebAuthn credential created, the user can now use this method for future authentications. When logging in:

    • The user will be prompted to authenticate using their local authenticator.
    • The server verifies the authentication using the stored public key, ensuring a secure and swift login process.

Creating a WebAuthn credential through the eeID service not only enhances the security of online transactions but also offers a user-friendly authentication experience. It represents a forward step in secure, password-less digital authentication, promoting ease of use without compromising on security.

Requests

Authentication request

An authentication request is a HTTP GET request by which the user is redirected from the client application to the eeID server for authentication.

URL: https://auth.eeid.ee/hydra-public/oauth2/auth

Required query parameters:

Scope Authentication method
idcard EE ID-card
mid Mobile-ID
smartid Smart-ID
eidas EU-Citizen
webauthn Webauthn

Optional query parameters:

An example of an authentication request:

GET https://auth.eeid.ee/hydra-public/oauth2/auth?client_id=oidc-b8ab3705-c25f-4271-b87d-ecf190aa4982-11
&redirect_uri=https%3A%2F%2Feservice.institution.ee%2Fcallback
&response_type=code
&scope=openid%20idcard%20mid%20smartid%20eidas
&state=f3b2c3e7f4cf0bed3a783ed6ece617e3

Redirect request

The redirect request is a HTTP GET request which is used to redirect the user back to the return address entered upon registration of the client application in eeID manager. In the redirect request an authorization code is sent to the client application, based on which the client application will request the access token in order to get personal identification code, name and other attributes of the authenticated person. The security code state received in the authentication request is mirrored back. Read more about forming and verifying state from Protection against false request attacks.

An example of a redirect request:

GET https://eservice.institution.ee/callback?code=71ed5797c3d957817d31&
state=OFfVLKu0kNbJ2EZk


Request might contain other URL parameters, that client application must ignore.

If eeID is unable to process an authentication request - there will be an error in the request. eeID transfers an error message (URL parameter error) and the description of the error (URL parameter error_description) in the redirect request:

GET https://eservice.institution.ee/callback?error=invalid_scope&error_description=
The+requested+scope+is+invalid%2C+unknown%2C+or+malformed.+The+OAuth+2.0+Client+is+not+allowed+to+request+scope+%27invalid_scope%27.
&state=0b60fe50138f8fdd56afd2a6ab7a40f9


The redirect request errors are normally resulted by a misconfiguration; therefore the error description in parameter error_description is not needed to be displayed for the user directly. The client application should check whether or not an error message has been sent.

Identity token request

The identity token request is an HTTP POST request which is used by the client application to request the identity token from the login server of eeID.

An example of an identity token request:

POST https://auth.eeid.ee/hydra-public/oauth2/token
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content_Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=71ed5797c3d957817d31&
redirect_uri=https%3A%2F%2Feservice.institution.ee%2Fcallback


The client secret code must be provided in the identity token request. For this purpose, the request must include the Authorization request header with the value formed of the word Basic, a space and a string <client_id>:<client_secret> encoded in the Base64 format. The body of the HTTP POST request must be presented in a serialised format based on the OpenID Connect protocol. The body of the request must include the code received from the authentication service.

The body of the request must include the following parameters:

Element Description
grant_type The authorization_code value required based on the protocol
code The authorization code received from the authentication service
redirect_url The redirect URL sent in the authorisation request

The server verifies that the identity token is requested by the right application and issues the identity token included in the response body. The response body uses JSON format consisting four elements:

Element Description
access_token OAuth 2.0 access certificate. With access token the client application can issue authenticated user’s data from userinfo endpoint.
token_type OAuth 2.0 access token type with bearer value
expires_in The validity period of the OAuth 2.0 access token
id_token Identity token. Presented in JWS Compact Serialization form

The identity token is a certificate of the fact of authentication issued by eeID. The identity token is issued in JSON Web Token, JWT format. The identity token is always signed. Example:

{
  "jti": "0c597356-3771-4315-a129-c7bc1f02a1b2",
  "iss": "https://auth.eeid.ee",
  "aud": "oidc-b8ab3705-c25f-4271-b87d-ecf190aa4982-12",
  "exp": 1530295852,
  "iat": 1530267052,
  "nbf": 1530266752,
  "sub": "EE60001019906",
  "profile_attributes": {
    "date_of_birth": "2000-01-01",
    "family_name": "O’CONNEŽ-ŠUSLIK TESTNUMBER",
    "given_name": "MARY ÄNN"
  },
  "amr": [
    "mID"
  ],
  "state": "1OnH3qwltWy81fKqcmjYTqnco9yVQ2gGZXws/DBLNvQ=",
  "nonce": "",
  "at_hash": "X0MVjwrmMQs/IBzfU2osvw=="
}


The following claims are presented in the identity token:

JSON element (claim) Description
jti Identity token identifier
iss Issuer of the certificate
aud ID of a client application that requested authentication (the value of client_id field specified upon directing the user to the authentication process)
exp 1530295852 - expiration time of the certificate (in Unix epoch format)
iat 1530295852 - time of issue of the certificate (in Unix epoch format)
nbf 1530295852 - validity start time of the certificate (in Unix epoch format)
sub The identifier of the authenticated user (personal identification code or eIDAS identifier) with the prefix of the country code of the citizen (country codes based on the ISO 3166-1 alpha-2 standard).
profile_attributes The data of the authenticated user, including the eIDAS attributes
profile_attributes.date_of_birth 2000-01-01 - the date of birth of the authenticated user in the ISO_8601 format.
profile_attributes.given_name The first name of the authenticated user
profile_attributes.family_name The surname of the authenticated user
amr The authentication method used for user authentication. Example values: mID - Mobile-ID, idcard - Estonian ID card, eIDAS - cross-border, smartid - Smart-ID
acr high - level of authentication based on the eIDAS LoA (level of assurance). Possible values: low, substantial, high
state The authentication request's state parameter value
nonce The authentication request's nonce parameter value. Value is present only in case the nonce parameter was sent in the authentication request
at_hash The access token hash (not used)
email The user's e-mail address (if present)
email_verified false - the e-mail address of the user has not been verified
phone_number +37200000766 - the phone number is presented in E.164 format and prefixed by a country code (if present)
phone_number_verified true - the ownership of the phone number to the authenticating user has been confirmed

Identity token might consist of other OpenID Connect protocol based fields that are not supported by eeID.

The client application must obtain the identity token immediately or within 30 seconds (before the expiry time of the identity token).

User info request

User info request enables requesting information about an authenticated user based on a valid OAuth 2.0 access token. The request must be done by using the HTTP GET method. The access token must be presented to the user info endpoint in the HTTP header by using the Bearer Token method or as a URLi parameter.

Example 1 - transferring an access certificate in the Authorization header:

GET https://auth.eeid.ee/hydra-public/userinfo
Auhtorization: Bearer AT-20-qWuioSEtFhYVdW89JJ4yWvtI5SaNWep0


Example 2 – transferring of access certificate as an access_token parameter:

GET https://auth.eeid.ee/hydra-public/userinfo?access_token=AT-20-qWuioSEtFhYVdW89JJ4yWvtI5SaNWep0


The valid access token response is provided in the JSON format. Example:

{
  "acr": "high",
  "auth_time": 1694591147,
  "authentication_type": "SMART_ID",
  "date_of_birth": "2000-01-01",
  "family_name": "O’CONNEŽ-ŠUSLIK TESTNUMBER",
  "given_name": "MARY ÄNN",
  "sub": "EE60001019906",
}


The claims included in the response are issued based on the identity token.

JSON element (claim) Description
auth_time The time of successful authentication of the user
sub The identifier of the authenticated user (personal identification code or eIDAS identifier) with the prefix of the country code of the citizen (country codes based on the ISO 3166-1 alpha-2 standard)
authentication_type The authentication method used for user authentication. Example values: MOBILE_ID - Mobile-ID, ID_CARD - Estonian ID card, SMART_ID - Smart-ID, WEBAUTHN - Fido Webauthn
acr high - level of authentication based on the eIDAS LoA (level of assurance). Possible values: low, substantial, high
date_of_birth The date of birth of the authenticated user in the ISO_8601 format
given_name The first name of the authenticated user
family_name The surname of the authenticated user

Response body might contain other fields, that client application may ignore.

In case the access token presented to the user information endpoint is missing or is expired, an error code and a brief description about the error are returned:

{
  "error": "invalid_token",
  "error_description": "Token expired. Access token expired at '2022-10-07 14:55:34 +0000 UTC'."
}

Protection

The client application must implement protective measures against false request attacks (cross-site request forgery, CSRF). This can be achieved by using state security code. Using state is compulsory.

Using state with a cookie set on the client application side means that the client application itself does not have to remember the state parameter value. The process is described below.

The state security code is used to combat falsification of the redirect request following the authentication request. The client application must perform the following steps:

  1. Generate a random hexadecimal state session key, for example of the length of 32 characters: 07f19702e7e9591c6fa2554e1fcf5f4a (referred to as R).
  2. Add an order to set a cookie for the client application domain with a value of R immediately before making the authentication request, for example:

Set-Cookie ESERVICE=07f19702e7e9591c6fa2554e1fcf5f4a; HttpOnly Where ESERVICE is a freely selected cookie name. The HttpOnly attribute must be applied to the cookie.

  1. Set the following value, in the authentication request, for the state parameter calculated based on section 1:
state=07f19702e7e9591c6fa2554e1fcf5f4a


Length of state parameter must be minimally 8 characters. In the course of processing the redirect request, the client application must:

  1. Take the ESERVICE value of the cookie received with the request.
  2. Verify that the ESERVICE value matches the state value mirrored back in the redirect request.

The redirect request may only be accepted if the checks described above are successful. The key element of the process described above is connection of the state value with the session. This is achieved by using a cookie.

Endpoints and timeouts

Production service

Endpoint URL
server discovery https://auth.eeid.ee/hydra-public/.well-known/openid-configuration
public signature key https://auth.eeid.ee/hydra-public/.well-known/jwks.json
authorization https://auth.eeid.ee/hydra-public/oauth2/auth
token https://auth.eeid.ee/hydra-public/oauth2/token
userinfo https://auth.eeid.ee/hydra-public/userinfo

Test service

Endpoint URL
server discovery https://test-auth.eeid.ee/hydra-public/.well-known/openid-configuration
public signature key https://test-auth.eeid.ee/hydra-public/.well-known/jwks.json
authorization https://test-auth.eeid.ee/hydra-public/oauth2/auth
token https://test-auth.eeid.ee/hydra-public/oauth2/token
userinfo https://test-auth.eeid.ee/hydra-public/userinfo

Timeouts

Timeout Value Remark
session 30 min eeID server creates a session with the user identified. If the user doesn’t perform any activity on eeID page, the session will expire in 30 minutes. Note: eeID session must be distinguished from the session between the client application and the user.
SSL/TLS handshake 25 s In case of ID-card authentication. The user must enter PIN1 within 25 seconds. After the timeout, the authentication will be terminated for security reasons.
Authorization code 30 s The client application must obtain the access token using authorization code within 30 seconds.

Testing

A prerequisite for testing the eeID authentication service is registering a service in test environment. After approving your service, it is possible to test the service immediately, using the credentials generated after approving.

Users for successful authentication:

Mobile ID

The eeID test environment is directed to the Mobiil-ID demo environment. Public test numbers are available for use:

Smart ID

The eeID test environment is directed to the Smart-ID demo environment. There are two options for use:

Code examples

OpenID Connect Client with Spring Security

One of the key features of Spring Security 5 was the native support for OAuth2 and OIDC. Making use of the OIDC configuration information (OIDC metadata), integrating with the eeID Server gets super easy. This tutorial shows how to use a registered service to login via eeID and access the user details within an ID-token.

Prerequisites

You should be familiar with Java, Spring Boot, and Spring Security. Optionally, you should know how to use IntelliJ IDEA, but you can use any IDE of your choice. Make sure you configure a service in the eeID manager before getting started.

Setting up the project

  1. Visit start.spring.io to create a new Spring Boot project
  2. Select Maven as your build tool and Java as your language
  3. Change the group to something meaningful and name your project
  4. Choose JDK 17 (or the latest available)
  5. Search for and add the following dependencies: Spring Security, OAuth2 Client, Spring Reactive Web, Thymeleaf

Spring Initializr 6. Generate the application. Spring Initializr creates an archive with a bootstrap application that includes the selected dependencies. Download and extract the archive, and import the project in an IDE of your choice

Add a Starting Site

Provide a starting site that is publicly available. Create the file src/main/resources/templates/index.html. Add a link to the protected resource /secured.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Spring Boot eeID Demo</title>
</head>

<body>
<h1>Welcome</h1>
<p><a href="/secured">Secured content</a></p>
</body>
</html>

Add a Controller

When the user logs in show the username. For that create a controller that handles requests for the endpoint / and /secured. Create the file src/main/java/com/example/demo/UserController.java:

@Controller
public class UserController {
    @GetMapping("/")
    public String index(){
        return "index";
    }

    @GetMapping("/secured")
    public String user(Model model,
                        @AuthenticationPrincipal OidcUser oidcUser) {
        model.addAttribute("userName", oidcUser.getName());
        model.addAttribute("audience", oidcUser.getAudience());
        return "secured";
    }
}


Create a template called secured.html next to index.html. Output the attributes for the username and client ID.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Spring Boot eeID Demo - Login</title>
    <meta charset="utf-8" />
</head>
<body>

<h1>Your Login Details</h1>
<div>
    Welcome <span style="font-weight:bold" th:text="${userName}"/>!
    You logged in at the OAuth 2.0 Client <span style="font-weight:bold" th:text="${audience}"/>.
</div>
</body>
</html>


With these routes in place, we can now set up our security configuration.

Protect the User Area

So far there are two unprotected endpoints: / and /secured. Create another class, that enforces OAuth for certain paths. Create the file src/main/java/com/example/demo/OAuth2SecurityConfig.java with the following content:

@Configuration
@EnableWebFluxSecurity
public class OAuth2SecurityConfig {

    @Bean
    public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
        http
            .authorizeExchange(authorize -> authorize
                .pathMatchers("/", "/error").permitAll()
                .anyExchange().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .authenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}"))
            );
        return http.build();
    }
}


This enables and configures Spring Web Security. The endpoints / and /error are public. Any other requests must be authenticated using OAuth. Spring Security creates a default login page at /login that lists all the login options.

Configure the OAuth Client

Define the following client in src/main/resources/application.yml:

spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: demo
  security:
    oauth2:
      client:
        registration:
          eeid:
            client-name: Login with the eeID
            client-id: <your-eeid-client-id>
            client-secret: <your-eeid-secret>
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/callback/{registrationId}"
            scope: openid,idcard,mid,smartid,eidas
        provider:
          eeid:
            issuer-uri: https://test-auth.eeid.ee/hydra-public/


This triggers Spring Boot to register a client. The client registration gets the id eeid which is part of the (default) redirect-uri. The remaining properties, client-id, client-secret and scope have been defined when configuring the client in the eeID manager (see Getting Started). Make sure you configured correct callback url which is in our case must be http://localhost:8080/login/oauth2/callback/eeid. You can choose any descriptive client-name. This is the string that is used in the default login page setup at /login.
Spring Boot Security loads all the necessary OpenID configuration from the metadata at https://test-auth.eeid.ee/hydra-public/.well-known/openid-configuration and ensures that the user-agent gets redirected to the right endpoints for authentication.

Run the Demo Application

Start the demo application with mvn spring-boot:run. Navigate to http://localhost:8080 to access the index site. Click on the link to access http://localhost:8080/secured that triggers a login.

Login Screen
After successful login the page shows details retrieved from the ID token.

Login Details
You can also navigate to http://localhost:8080/login to directly access the default login page created by Spring Security.

For further examples and help regarding OAuth2 and Spring Security for a reactive web application visit Spring Security Reference Documentation.

PHP OpenID Connect example

Make sure you configure a service in the eeID manager before getting started.
This example uses the jumbojett basic OpenID Connect client and phpdotenv installed using composer and running on Docker container.

It takes users to an attributes page after login and display the claims/values that have been passed. In the real world you would read the claims and feed them into your authorisation/user-session management process.

Instructions

  1. Start by creating a new Dockerfile. This file will be used to build an image for your container. In the Dockerfile, include the following lines::
FROM php:7.4-apache
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
RUN apt-get update && apt-get install -y \
    zip \
    unzip
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer


This will use the php:7.4-apache image as the base for your container and install the necessary dependencies for Composer (zip and unzip). It will then use curl to download the Composer installer and run it to install Composer in the /usr/local/bin directory.

2. Create docker-compose.yml file in your project folder:

version: '3.1'

services:
  app:
    build:
      context: .
    ports:
      - "8082:80"
    volumes:
      - .:/var/www/html
    environment:
      - APACHE_DOCUMENT_ROOT=/var/www/html


3. Create composer.json

{
    "require": {
        "jumbojett/openid-connect-php": "0.8.0",
        "vlucas/phpdotenv": "^5.3"
    }
}


4. Build the image and run a new container

dockder-compose up --build


5. Run composer to read the composer.json file from the current directory, resolve the dependencies and install them into vendor:

docker-compose run --rm app composer install


6. Create a php page to handle the login, e.g. index.php. This one creates a session attribute of an array of the returned claims and then passes the user to an attributes.php page where they can be displayed.

<?php

require 'vendor/autoload.php';

use Jumbojett\OpenIDConnectClient;

$dotenv = \Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

$issuer = getenv('ISSUER');
$cid = getenv('OAUTH_CLIENT_ID');
$secret = getenv('OAUTH_CLIENT_SECRET');
$redirectUrl = getenv('CLIENT_REDIRECT_URI');
$scope = getenv('SCOPE');

$oidc = new OpenIDConnectClient($issuer, $cid, $secret);
$oidc->setRedirectURL($redirectUrl);
$oidc->addScope($scope);
$oidc->addAuthParam(['ui_locales' => 'en']);

$oidc->authenticate();

$profile = $oidc->getVerifiedClaims('profile_attributes');

foreach($profile as $key=>$value) {
    if(is_array($value)){
            $v = implode(', ', $value);
    }else{
            $v = $value;
    }
    $session[$key] = $v;
}


session_start();
$_SESSION['attributes'] = $session;

header("Location: ./attributes.php");

?>


7. Create .env file with your OAuth client configuration parameters

ISSUER="https://test-auth.eeid.ee/hydra-public/"
OAUTH_CLIENT_ID="<your-eeid-client-id>"
OAUTH_CLIENT_SECRET="<your-eeid-secret>"
CLIENT_REDIRECT_URI="http://localhost:8082/index.php"
AUTHORIZATION_SERVER_AUTHORIZE_URL="https://test-auth.eeid.ee/hydra-public/oauth2/auth"
AUTHORIZATION_SERVER_ACCESS_TOKEN_URL="https://test-auth.eeid.ee/hydra-public/oauth2/token"
SCOPE="openid idcard mid smartid eidas"


Parameters like OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET and SCOPE have been defined after configuring the service in the eeID manager (see Getting Started).
8. Add the attributes.php page. E.g:

<?php
    session_start();
?>
<!DOCTYPE html>
<html lang="en">

<head>

   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <meta name="description" content="">
   <meta name="author" content="">

   <title>OpenID Connect: Released Attributes</title>

</head>

<body>

   <!-- Intro -->
   <div class="banner">
      <div class="container">

         <h3>
            Claims sent back from OpenID Connect
         </h3>
         <br/>
      </div>
   </div>

   <!-- Claims -->
   <div class="content-section-a" id="openAthensClaims">
      <div class="container">
         <div class="row">

               <table class="table" style="width:80%;" border = "1">
                 <?php foreach ($_SESSION['attributes'] as $key=>$value): ?>
                      <tr>
                          <td data-toggle="tooltip" title=<?php echo $key; ?>><?php echo $key; ?></td>
                          <td data-toggle="tooltip" title=<?php echo $value; ?>><?php echo $value; ?></td>
                      </tr>
                 <?php endforeach; ?>

               </table>
         </div>
      </div>
   </div>
</body>

</html>


9. Go to http://localhost:8082/index.php in a browser. You will be sent to an eeID sign-in page. After signing in you will be sent back and then on the attributes page.

Claims

Rails on Rails with OmniAuth-Tara

In this example, we will make use of OmniAuth-Tara gem, which contains the Tara strategy for OmniAuth library that standardizes multi-provider authentication for web applications.

Getting Started

Start by generating your Rails application with Bootstrap using ESBuild to build both the JavaScript and CSS files. From the terminal, run the command to do so:

rails new eeid_demo -T -j esbuild --css bootstrap


Create a partial named _navigation.html.erb to hold your navigation code. The partial should be located in the app/views/layouts directory. Enter the code below into an IDE. It uses Bootstrap to create a navigation bar for your application.

<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">eeID Demo</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <%= link_to 'Home', root_path, class: 'nav-link active' %>
        </li>
      </ul>
    </div>
  </div>
</nav>


For the navigation to be used, we need to render it in the application layout. Change application layout to look like this:

<!DOCTYPE html>
<html>
  <head>
    <title>eeID Demo</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
  </head>

  <body>
    <%= render "layouts/navigation" %>
    <% flash.each do |key, value| %>
      <div class="<%= flash_class(key) %>">
        <%= value %>
      </div>
    <% end %>
    <div class="container-fluid">
      <%= yield %>
    </div>
  </body>
</html>


In order to display flash messages with the Bootstrap alert styles, extend application_helper.rb with the following method:

def flash_class(level)
  flash_classes = {
    notice: 'alert alert-info',
    success: 'alert alert-success',
    error: 'alert alert-error',
    alert: 'alert alert-error'
  }

  flash_classes[level.to_sym]
end


Generate a PagesController with an index action by entering the command below into your terminal.

rails generate controller Pages index


In the index view generated, edit it to look like this:

<div class="jumbotron">
  <h1>Welcome to eeID Demo!</h1>
</div>


Open routes file to add our root_path:

# config/routes.rb

Rails.application.routes.draw do
  root to: 'pages#index'
end

Setting Up OmniAuth-Tara

We need to create a new eeID service application. Go to eeID manager to create one. Enter all the necessary details:

Demo Service

For the callback URL, enter your website's address plus auth/tara/callback. If you happen to be on a local machine, your callback URL should be this: http://127.0.0.1:3000/auth/tara/callback

After submitting you will be redirected to the service information page. Copy the Client ID and Client Secret and paste them in a safe place — we will make use of them shortly. The callback URL is the URL where a user will be redirected to inside the app after successful authentication and approved authorization (the request will also contain the user’s token). All OmniAuth strategies expect the callback URL to equal /auth/:provider/callback. :provider takes the name of the strategy. In our case, the strategy will be tara as you will list in the initializer.

Open up Gemfile to add the necessary gems:

# Gemfile
...
gem 'omniauth', '>=2.0.0'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-tara', github: 'internetee/omniauth-tara'


Now create an initializer for OmniAuth in your config/initializers directory. This will hold the configuration for OmniAuth:

# config/initializers/omniauth.rb

# Block GET requests to avoid exposing self to CVE-2015-9284
OmniAuth.config.allowed_request_methods = [:post]

Rails.application.config.middleware.use OmniAuth::Builder do
  provider 'tara', {
    name: 'tara',
    scope: ENV['SCOPES'].split(','),
    state: SecureRandom.hex(10),
    client_signing_alg: :RS256,
    send_scope_to_token_endpoint: false,
    send_nonce: true,
    issuer: ENV['ISSUER'],
    discovery: true,

    client_options: {
      identifier: ENV['IDENTIFIER'],
      secret: ENV['SECRET'],
      redirect_uri: ENV['REDIRECT_URL'],
    },
  }
end


We need to keep all the necesary private data safe as we do not want to push them to a public repository when we commit our code. We will make use of a special gem for this. Open Gemfile again and add the gem below. Add it to Gemfile like so:

# Gemfile
...
group :development, :test do
  ...
  gem 'dotenv-rails'
...


To install the gems, run:

bundle install


In the home directory create a file called .env.

# .env

ISSUER="https://test-auth.eeid.ee/hydra-public/"
IDENTIFIER="<your-eeid-client-id>"
SECRET="<your-eeid-secret>"
REDIRECT_URL="http://localhost:3000/auth/tara/callback"
SCOPES="openid,idcard,mid,smartid"


Open .gitignore and add the file we just created.

# .gitignore
...
# Ignore .env used for storing id and secret
.env


Time to work on our routes. Open up the routes file and add the route below:

# config/routes.rb

Rails.application.routes.draw do
  ...
  get '/auth/:provider/callback', to: 'sessions#create'
  ...
end


We need to add the link for eeID sign-in to navigation and to show this link only when the user is not signed in. Open navigation file and change it to look like this:

<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">eeID Demo</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <%= link_to 'Home', root_path, class: 'nav-link active' %>
        </li>
        <% if current_user %>
          <li class="nav-item nav-link">Signed in as <%= current_user.first_name %></li>
        <% else %>
          <li class="nav-item"><%= button_to 'Sign in with eeID', '/auth/tara', class: 'nav-link', data: { turbo: false } %></li>
        <% end %>
      </ul>
    </div>
  </div>
</nav>

Creating Sessions

We'll need a session controller to handle the logging in of users. Create a file for that in controllers directory. The create action helps create a session for users so they can be logged into your application. Without this, users have no means of logging in.

# app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def create
    @user = User.find_or_create_from_auth_hash(auth_hash)
    session[:user_id] = @user.id
    flash[:success] = 'Sucessfully logged in!'
    redirect_to root_path
  end

  protected

  def auth_hash
    request.env['omniauth.auth']
  end
end


We'll need a current_user method at this point. This will help us check if a user is logged in or out. Open app/controllers/application_controller.rb and add the following:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  ...
  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end

  helper_method :current_user
  ...
end

User Model

Now generate a model for Users. Run this command to do so:

rails generate model User provider:string uid:string first_name:string last_name:string token:string


That should generate a migration file that looks like this:

class CreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      t.string :provider
      t.string :uid
      t.string :first_name
      t.string :last_name
      t.string :token

      t.timestamps
    end
  end
end


Now migrate database by running:

rake db:migrate


Open up User model and make it look like this:

# app/models/user.rb

class User < ApplicationRecord
  def self.find_or_create_from_auth_hash(auth_hash)
    user = where(provider: auth_hash.provider, uid: auth_hash.uid).first_or_create
    user.update(
      first_name: auth_hash.info.first_name,
      last_name: auth_hash.info.last_name,
      token: auth_hash.credentials.token,
    )
    user
  end
end


The code above stores some information belonging to the user. This includes the first_name, last_name and token of the user.

Deleting Sessions

In our application, we want to provide users the ability to log out. We will need a destroy action in SessionsController for this to work. Then a link will be added to navigation.

Add the destroy action to SessionsController:

# app/controllers/sessions_controller.rb

class SessionsController < ApplicationController  
  ...
  def destroy
    if current_user
      session.delete(:user_id)
      flash[:success] = "Sucessfully logged out!"
    end
    redirect_to root_path
  end
  ...
end


Then add this link for logging out to navigation, so our navigation looks like this:

<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">eeID Demo</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <%= link_to 'Home', root_path, class: 'nav-link active' %>
        </li>
        <% if current_user %>
          <li class="nav-item nav-link">Signed in as <%= current_user.first_name %></li>
          <li class="nav-item"><%= button_to 'Log Out', logout_path, method: :delete, class: 'nav-link', data: { turbo: false } %></li>
        <% else %>
          <li class="nav-item"><%= button_to 'Sign in with eeID', '/auth/tara', class: 'nav-link', data: { turbo: false } %></li>
        <% end %>
      </ul>
    </div>
  </div>
</nav>


Open up config/routes.rb to update our routes with the action we just created.

Rails.application.routes.draw do
  ...
  delete '/logout', to: 'sessions#destroy'
  ...
end


Start up rails server and point browser to http://localhost:3000.

bin/dev