Preface
Suomi.fi e-Identification is a shared strong identification service of public administration e-services. Keycloak is a popular open-source identity and access management solution.
They are commonly used together for authentication in services developed for authorities, public agencies, health care, municipalities etc. Usually in a project the authentication is set up once quite early and the details might get lost in time. This guide aims to provide simple step-by-step instructions on how to configure everything from scratch.
Prerequisites
- Keycloak
- Access to the Keycloak admin console
- Access to Suomi.fi Palveluhallinta (directly or by proxy)
What this guide does not cover
- How to configure Keycloak for your application
- How to integrate with AWS Cognito (a common combination)
Good to know
We use Suomi.fi test environment for this guide, but the steps should be more or less the same for production as well. You can use any existing realm in Keycloak or create your own, depending on your configuration.
Certificates
Let’s start by generating self-signed certificates (Suomi.fi test environment accepts self-signed). We’ll be using separate certificates for signing and encryption.
For example, using openssl
:
openssl req -newkey rsa:2048 -nodes -keyout sig.key -x509 -days 365 -out sig.crt -subj "/C=FI/O=Solita/CN=example.com"
openssl req -newkey rsa:2048 -nodes -keyout enc.key -x509 -days 365 -out enc.crt -subj "/C=FI/O=Solita/CN=example.com"
Obviously, real certificates should be used in production, but we’ll leave them as homework.
Add the certificates to Keycloak
Open the Keycloak admin console, choose your realm and go to Realm settings > Keys > Add providers.
First, remove providers rsa-generated
and rsa-enc-generated
so that they don’t interfere with our configuration.
After they’re gone, let’s add our generated certificates to our realm.
Add new provider of type rsa
:
- Name:
suomi.fi sig cert
- Priority:
0
- Enabled:
true
- Active:
true
- Private RSA Key: upload
sig.key
created earlier - X509 Certificate: upload
sig.crt
created earlier - Algorithm:
RS256
Add new provider of type rsa-enc
:
- Name:
suomi.fi enc cert
- Priority:
0
- Enabled:
true
- Active:
true
- Private RSA Key: upload
enc.key
created earlier - X509 Certificate: upload
enc.crt
created earlier - Algorithm:
RSA-OAEP
SAML v2.0 identity provider
Now that we have our certificates in order, we can add Suomi.fi as an identity provider. Keycloak is already the identity provider (IDP) for your application, but in this case, it also is the service provider (SP) for Suomi.fi. And respectively, Suomi.fi is the identity provider for Keycloak.
Open the Keycloak admin console, choose your realm and go to Identity providers.
Add new identity provider of type SAML v2.0
:
- Alias:
suomi.fi
- Display name:
Suomi.fi
- Display order:
1
- Service provider entity ID: usually the URL of your environment, e.g. http://keycloak-demo-application
(Must be an URL, but it does not need to actually exist. This will identify the service in Suomi.fi and must be globally unique.) - Use entity descriptor:
true
- SAML entity descriptor:
https://static.apro.tunnistus.fi/static/metadata/idp-metadata.xml
(IDP metadata addresses at the bottom of the page)
(Don’t save yet, we want to tweak the settings first a bit)
We’ll configure the identity provider to use social security number (SSN) to identify external users.
Switch Use entity descriptor off and edit the following values:
- Principal type:
Attribute [Name]
- Principal attribute:
urn:oid:1.2.246.21
(see Attributes of the identified user) - Want Assertions signed:
true
- Want Assertions encrypted:
true
- Encryption Algorithm:
RSA-OAEP
(matches the configured encryption key)
Attribute mappers
We can extract user information from the identity provided by Suomi.fi with attribute mappers. Let’s add a few.
Open the Keycloak admin console, choose your realm and go to Identity providers > Suomi.fi > Mappers.
Add new mapper:
- Name:
ssn
- Sync mode override:
inherit
- Mapper type:
Attribute Importer
- Attribute Name:
urn:oid:1.2.246.21
- Friendly Name:
<empty>
- Name Format:
ATTRIBUTE_FORMAT_URI
- User Attribute Name:
username
Add new mapper:
- Name:
first names
- Sync mode override:
inherit
- Mapper type:
Attribute Importer
- Attribute Name:
http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName
- Friendly Name:
<empty>
- Name Format:
ATTRIBUTE_FORMAT_URI
- User Attribute Name:
firstName
Add new mapper:
- Name:
last name
- Sync mode override:
inherit
- Mapper type:
Attribute Importer
- Attribute Name:
urn:oid:2.5.4.4
- Friendly Name:
<empty>
- Name Format:
ATTRIBUTE_FORMAT_URI
- User Attribute Name:
lastName
Service provider metadata
Keycloak generates a service provider metadata but Suomi.fi has its own special requirements for the metadata. I’ve found that it’s usually easiest to just create the metadata manually based on the Suomi.fi example metadata.
There are a few things in the metadata we’ll need to customize:
EntityDescriptor
element’sentityID
attribute uniquely identifies the application. Must equal Service provider entity ID in Keycloak IDP settings.UIInfo
element contains a description of the environment. These are visible to the end user.SPSSODescriptor
element contains certificates asKeyDescriptor
elements. We use separate certificates foruse="signing"
anduse="encryption"
but it’s also possible to use one certificate for both purposes.SingleLogoutService
andAssertionConsumerService
elements’Location
attribute is the redirect address of the service provider (Keycloak). Must equal Redirect URI in Keycloak IDP settings.AttributeConsumingService
element contains the service provider name (which can be different from the one inUIInfo
element) and the attributes of the identified user that we want to request.Organization
element contains organization info.ContactPerson
elements contain contact information. At least technical contact information is mandatory.
Example metadata
A minimal example of a working metadata (using the “suppea tietosisältö” set of user attributes). Things to customize are marked with CHANGE_ME
.
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="CHANGE_ME">
<md:Extensions>
<mdattr:EntityAttributes xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute">
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="FinnishAuthMethod" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">http://ftn.ficora.fi/2017/loa3</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">http://ftn.ficora.fi/2017/loa2</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">http://eidas.europa.eu/LoA/high</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">http://eidas.europa.eu/LoA/substantial</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">urn:oid:1.2.246.517.3002.110.999</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" FriendlyName="VtjVerificationRequired" Name="urn:oid:1.2.246.517.3003.111.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">true</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" FriendlyName="SkipEndpointValidationWhenSigned" Name="urn:oid:1.2.246.517.3003.111.4" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">false</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" FriendlyName="CipherName" Name="urn:oid:1.2.246.517.3003.111.26" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">AES-GCM</saml:AttributeValue>
</saml:Attribute>
</mdattr:EntityAttributes>
</md:Extensions>
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:Extensions>
<mdui:UIInfo xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
<mdui:DisplayName xml:lang="fi">CHANGE_ME</mdui:DisplayName>
<mdui:DisplayName xml:lang="sv">CHANGE_ME</mdui:DisplayName>
<mdui:DisplayName xml:lang="en">CHANGE_ME</mdui:DisplayName>
<mdui:Description xml:lang="fi">CHANGE_ME</mdui:Description>
<mdui:Description xml:lang="sv">CHANGE_ME</mdui:Description>
<mdui:Description xml:lang="en">CHANGE_ME</mdui:Description>
<mdui:PrivacyStatementURL xml:lang="fi">CHANGE_ME</mdui:PrivacyStatementURL>
<mdui:PrivacyStatementURL xml:lang="sv">CHANGE_ME</mdui:PrivacyStatementURL>
<mdui:PrivacyStatementURL xml:lang="en">CHANGE_ME</mdui:PrivacyStatementURL>
</mdui:UIInfo>
</md:Extensions>
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>CHANGE_ME</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>CHANGE_ME</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
<md:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
<md:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#rsa-oaep"/>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="CHANGE_ME"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="CHANGE_ME" isDefault="true" index="1"/>
<md:AttributeConsumingService index="1" isDefault="true">
<md:ServiceName xml:lang="fi">CHANGE_ME</md:ServiceName>
<md:ServiceName xml:lang="sv">CHANGE_ME</md:ServiceName>
<md:ServiceName xml:lang="en">CHANGE_ME</md:ServiceName>
<md:RequestedAttribute FriendlyName="FamilyName" Name="http://eidas.europa.eu/attributes/naturalperson/CurrentFamilyName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="FirstName" Name="http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="DateOfBirth" Name="http://eidas.europa.eu/attributes/naturalperson/DateOfBirth" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="PersonIdentifier" Name="http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="nationalIdentificationNumber" Name="urn:oid:1.2.246.21" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="displayName" Name="urn:oid:2.16.840.1.113730.3.1.241" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="cn" Name="urn:oid:2.5.4.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="sn" Name="urn:oid:2.5.4.4" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
<md:RequestedAttribute FriendlyName="givenName" Name="urn:oid:2.5.4.42" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
</md:AttributeConsumingService>
</md:SPSSODescriptor>
<md:Organization>
<md:OrganizationName xml:lang="fi">CHANGE_ME</md:OrganizationName>
<md:OrganizationName xml:lang="sv">CHANGE_ME</md:OrganizationName>
<md:OrganizationName xml:lang="en">CHANGE_ME</md:OrganizationName>
<md:OrganizationDisplayName xml:lang="fi">CHANGE_ME</md:OrganizationDisplayName>
<md:OrganizationDisplayName xml:lang="sv">CHANGE_ME</md:OrganizationDisplayName>
<md:OrganizationDisplayName xml:lang="en">CHANGE_ME</md:OrganizationDisplayName>
<md:OrganizationURL xml:lang="fi">CHANGE_ME</md:OrganizationURL>
<md:OrganizationURL xml:lang="sv">CHANGE_ME</md:OrganizationURL>
<md:OrganizationURL xml:lang="en">CHANGE_ME</md:OrganizationURL>
</md:Organization>
<md:ContactPerson contactType="technical">
<md:GivenName>CHANGE_ME</md:GivenName>
<md:SurName>CHANGE_ME</md:SurName>
<md:EmailAddress>mailto:CHANGE_ME</md:EmailAddress>
</md:ContactPerson>
</md:EntityDescriptor>
Upload metadata
The prepared metadata must be uploaded to Suomi.fi Palveluhallinta. Do it through your proxy (usually the client) or do it yourself, if you’ve been authorized.
After the metadata has been approved and published, we should be good to go for testing.
Testing
With “basic” settings in Keycloak, Suomi.fi will appear as an alternative way to log in.
We left the user’s email address out of the attributes and in this case, Keycloak will ask for it when the user logs in for the first time.
You’ll probably want to configure this (and many other things) differently but that is a story for another time.
Closing thoughts
This all might seem very complicated at first, but after you’ve practiced it a couple of times, you’ll find it’s not rocket science. The biggest hurdle for me personally was the loose coupling between the Keycloak identity provider configuration and the service provider metadata for Suomi.fi. I hope this post clarifies the connection between the two and provides a useful reference for future projects.
Disclaimer
Suomi.fi is launching a new dedicated site for developers on October 30th, 2024 and the links to support articles in this guide might or might not work after that.