Authentication using Swedish BankID

Authentication using Swedish BankID

More information

You can find more information on BankID here: https://www.bankid.com/en/om-bankid/bankid-in-my-services - to use it outside testing, you will need to sign an agreement with a selling BankID Bank - see details at https://www.bankid.com

Configuration

You can enable authentication using the Swedish BankID by enabling the authentication plugin:

dk.itp.security.authentication.bankid.se.BankIDSEAuthenticationPlugin

You do this by adding it to the session controllers configuration, to the authentication plugins property.

Once added, you need to provide additional configuration to it, here is an example that works with the test environment:

<group name="BankID SE" description="Swedish BankID"> <property name="bankid.se.acceptedsslcerts" value="${portalprotect.home}/config/x509/bankid/servercert_test.cer" description="Valid server SSL certificate CA"/> <property name="bankid.se.keystore.file" value="${portalprotect.home}/config/x509/bankid/FPTestcert2_20150818_102329.pfx" description="Keystore containing SSL client certificate and private key"/> <property name="bankid.se.keystore.password" value="qwerty123" description="Filename containing GeoIP ISP database"/> <property name="bankid.se.url" value="https://appapi2.test.bankid.com/rp/v4" description="URL to BankID Server"/> </group>

Below, is the complete list of possible configuration entries:

Property

Default value

Description

Property

Default value

Description

bankid.se.url

 

URL for the BankID Servers

bankid.se.keystore.provider

BC

Name of JCE provider to use when loading SSL client certificate keystore

bankid.se.keystore.type

PKCS12

Keystore type, usually PKCS12, JKS or luna

bankid.se.keystore.file

 

Name of keystore file

bankid.se.keystore.password

 

Password to keystore, can optionally be encrypted/obfuscated - see Encrypting or Obfuscating Passwords

bankid.se.keystore.privkeyalias

 

Alias of private key to use - if not specified, first key in the keystore is used

bankid.se.keystore.certalias

 

Certificate Alias

bankid.se.sslprovidername

 

Name of SSL provider if the default in the JVM should not be used

bankid.se.ssl.protocol

TLSv1.2

SSL protocol to use

bankid.se.ssl.verifycert

true

Set to false to disable SSL server certificate

bankid.se.ssl.verifyhostname

true

Set to false to disable verification that hostname matches certificate

bankid.se.acceptedsslcerts

 

List of trusted SSL server signing certificates

bankid.se.ignoreipaddress

false

If set to true, clients IP address is not send to BankID - in test, we have seen BankID return an

http.proxyHost

 

Proxy host to use when sending requests

http.proxyPort

 

Proxy port to use when sending requests

http.proxyUser
http.proxyPassword

 

If proxy is used, optional userid and password for it

http.noProxyFor

 

Hostnames or IPs matching this pattern will bypass the proxy

Integrating with GUI

When calling the Agent.logon() method, it will initially fail with errorcode AuthErrorCodes.ERROR_NOT_YET_COMPLETED to check if authentication succeeds, we should continually call login (with a delay of at least 2 seconds between calls) until it succeeds or fails with a different error.

Below is a brief example of parts of a Vaadin based application which initiates authentication, and repeatedly polls to obtain the status.

It starts by letting the user choose if he wants to authenticate using the same device he is on versus a(nother) mobile device - if it is the same device, we do not need to ask for his social security number, but we need it if he is using a different device.
For a different device, we prompt him for his social security number before initiating login.

/** * * Login using BankID SE * * All texts, flows and documentation is available at * * https://www.bankid.com/assets/bankid/rp/bankid-relying-party-guidelines-v2.15.pdf * * @author Kim Rasmussen * @version $Revision$ * * <pre> * Ceptor - http://ceptor.io * * This source code is confidential. * </pre> */ public class SwedishBankIDAuth extends VerticalLayout { private static final long serialVersionUID = 1L; private IController controller; private TextField personNummer; private Button next; private Button thisComputer; private Button mobileDevice; private Refresher refresher; private boolean isIdProvided = false; private boolean isMobileDevice = DeviceUtils.isMobileDevice(); public SwedishBankIDAuth(IController controller) { this.controller = controller; setStyleName("small-padding"); setWidth("100%"); setMargin(false); populateInitialGUI(); } private void populateInitialGUI() { removeAllComponents(); // Differ messages depending on running in browser or on mobile String promptKey = isMobileDevice ? "RFA20" : "RFA19"; addComponent(new Label(Language.getText(promptKey, promptKey))); thisComputer = new Button(Language.getText(promptKey+"_THIS", promptKey+"_THIS"), e -> useThisComputer()); thisComputer.setStyleName("custom"); mobileDevice = new Button(Language.getText(promptKey+"_MOBILE", promptKey+"_MOBILE"), e -> useMobileDevice()); thisComputer.setStyleName("custom"); HorizontalLayout hl = new HorizontalLayout(); hl.addComponents(thisComputer, mobileDevice); addComponent(hl); // Start with clean slate try { Agent.getInstance().setStateVariable(JSPHelper.getSessionID(), "bankidse_orderRef", null, false); Agent.getInstance().setStateVariable(JSPHelper.getSessionID(), "bankidse_autoStartToken", null, false); } catch(PTException e) { // OK to ignore } } private void useThisComputer() { isIdProvided = false; // No ID needed, since we are using the same device try { // Credentials here ensure that only BankID mobile can be used. Agent.getInstance().logon(JSPHelper.getSessionID(), AuthTypes.AUTHTYPE_BANKID_SE, "", "{\"certificatePolicies\": [\"1.2.752.78.1.5\"]}"); // TODO: Need to consider test environment vs production } catch(PTException e) { if (e.getErrorCode() != AuthErrorCodes.ERROR_NOT_YET_COMPLETED) { // Error 19 means that authentication is started handleError(e); return; } } redirectNow(); } private void redirectNow() { // If we get here, we can redirect try { String autoStartToken = Agent.getInstance().getStateVariable(JSPHelper.getSessionID(), "bankidse_autoStartToken"); String challenge = Base64.encode(UUID.randomUUID().toString()); Agent.getInstance().setStateVariable(JSPHelper.getSessionID(), "challenge", challenge, false); // Redirect to BankID on the same computer/device String url = "bankid:///?autostarttoken="+URLUtils.encodeURL(autoStartToken)+"&rpref="+URLUtils.encodeURL(challenge)+"&redirect="+URLUtils.encodeURL(Page.getCurrent().getLocation().toString()); // When we get back, we need to poll immediately to see if it worked. initiatePoll(); Page.getCurrent().setLocation(url); } catch (PTException e) { handleError(e); } } private void handleError(PTException e) { removeAllComponents(); if (refresher != null) { removeExtension(refresher); refresher = null; } Label l = new Label(getMessage(e)); l.addStyleName(ValoTheme.LABEL_FAILURE); addComponent(l); addComponent(new Button(Language.getText("bankid.retry", "Try again"), (source) -> populateInitialGUI())); } private void initiatePoll() { // Start the polling... refresher = new Refresher(); refresher.setRefreshInterval(2000); addExtension(refresher); refresher.addListener(source -> pollStatus()); } private void pollStatus() { try { // Credentials here ensure that only BankID mobile can be used. Agent.getInstance().logon(JSPHelper.getSessionID(), AuthTypes.AUTHTYPE_BANKID_SE, "", ""); if (refresher != null) { removeExtension(refresher); refresher = null; } // It worked, login is done. controller.bankIDLoginCompleted(); return; } catch(PTException e) { if (e.getErrorCode() != AuthErrorCodes.ERROR_NOT_YET_COMPLETED) { // Error 19 means that authentication is started handleError(e); return; } removeAllComponents(); addComponent(new Label(getMessage(e))); } } private String getMessage(PTException e) { String progressStatus = e.getErrorText(); if (progressStatus == null) return e.toString(); String message = progressStatus; switch(progressStatus) { case "OUTSTANDING_TRANSACTION": case "NO_CLIENT": message = Language.getText("RFA1", "RFA1"); break; case "ALREADY_IN_PROGRESS": case "CANCELLED": message = Language.getText("RFA3", "RFA3"); break; case "RETRY": case "INTERNAL_ERROR": message = Language.getText("RFA5", "RFA5"); break; case "USER_CANCEL": message = Language.getText("RFA6", "RFA6"); break; case "EXPIRED_TRANSACTION": message = Language.getText("RFA8", "RFA8"); break; case "USER_SIGN": message = Language.getText("RFA9", "RFA9"); break; case "CLIENT_ERR": message = Language.getText("RFA12", "RFA12"); break; case "STARTED": if (isMobileDevice) { if (isIdProvided) message = Language.getText("RFA14_B", "RFA14_B"); else message = Language.getText("RFA15_B", "RFA15_B"); } else { // PC is used if (isIdProvided) message = Language.getText("RFA14_A", "RFA14_A"); else message = Language.getText("RFA15_A", "RFA15_A"); } break; case "START_FAILED": message = Language.getText("RFA17", "RFA17"); break; } return message; } /** * Use mobile (or other device if already running on mobile) */ private void useMobileDevice() { // Prompt for ID setupPersonnummer(); } private void setupPersonnummer() { removeAllComponents(); personNummer = new TextField(); personNummer.setPlaceholder("ID"); personNummer.setIcon(VaadinIcons.USER); personNummer.setStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); personNummer.setWidth("100%"); addComponent(personNummer); next = new Button(Language.getText("RFA18", "RFA18"), e -> nextBtnClickListener()); next.setStyleName("custom"); addComponent(next); setComponentAlignment(next, Alignment.BOTTOM_RIGHT); } private void nextBtnClickListener() { if (personNummer.isEmpty()) { Notification.show(Language.getText("bankid.emptyid", "Please enter ID")); personNummer.focus(); return; } isIdProvided = true; try { Agent.getInstance().logon(JSPHelper.getSessionID(), AuthTypes.AUTHTYPE_BANKID_SE, personNummer.getValue().trim(), ""); controller.bankIDLoginCompleted(); } catch(PTException e) { if (e.getErrorCode() != AuthErrorCodes.ERROR_NOT_YET_COMPLETED) { // Error 19 means that authentication is started handleError(e); return; } } removeAllComponents(); addComponent(new Label(Language.getText("RFA1", "RFA1"))); initiatePoll(); } }

The language texts refer to this list, which is taken from BankID's suggestions:

#BankID settings - values come from https://www.bankid.com/assets/bankid/rp/bankid-relying-party-guidelines-v2.15.pdf bankidse.retry=Prøv igen bankidse.emptyid=Indtast venligst personnummer RFA1=Start your BankID app. RFA2=The BankID app is not installed. Please contact your internet bank. RFA3=Action cancelled. Please try again. RFA5=Internal error. Please try again. RFA6=Action cancelled. RFA8=The BankID app is not responding. Please check that the program is started and that you have internet access. If you don\u2019t have a valid BankID you can get one from your bank. Try again. RFA9=Enter your security code in the BankID app and select Identify or Sign. RFA12=Internal error. Update your BankID app and try again. RFA14_A=Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don\u2019t have a BankID which can be used for this login/signature on this computer. If you have a BankID card, please insert it into your card reader. If you don\u2019t have a BankID you can order one from your internet bank. If you have a BankID on another device you can start the BankID app on that device. RFA14_B=Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don\u2019t have a BankID which can be used for this login/signature on this device. If you don\u2019t have a BankID you can order one from your internet bank. If you have a BankID on another device you can start the BankID app on that device. RFA15_A=Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don\u2019t have a BankID which can be used for this login/signature on this computer. If you have a BankID card, please insert it into your card reader. If you don\u2019t have a BankID you can order one from your internet bank. RFA15_B=Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don\u2019t have a BankID which can be used for this login/signature on this device. If you don\u2019t have a BankID you can order one from your internet bank RFA16=The BankID you are trying to use is revoked or too old. Please use another BankID or order a new one from your internet bank. RFA17=The BankID app couldn\u2019t be found on your computer or mobile device. Please install it and order a BankID from your internet bank. Install the app from install.bankid.com. RFA18=Start the BankID app RFA19=Would you like to login or sign with a BankID on this computer or with a Mobile BankID? RFA20=Would you like to login or sign with a BankID on this device or with a BankID on another device? RFA19_THIS=This computer RFA20_THIS=This device RFA19_MOBILE=Mobile BankID RFA20_MOBILE=BankID on another device

State Variables Available in the Session After Authentication

Once authentication is completed, the following state variables are available within the session:

Name

Description

Name

Description

xmldsig

XML Signature containing the signed data from BankID

bankid_ocspResponse

Base64 encoded OCSP response as delivered from BankID

bankid_givenName

Users given name

bankid_surname

Users surname

bankid_name

Complete name

bankid_personalNumber

Users social security number

bankid_notBefore

Time of bankid authentication

bankid_notAfter

Time that bankid authentication expires (note that this value is not actively used by Ceptor)

bankid_ipAddress

IP address of users device used for BankID authentication.

 

When signing data, output of the sign operation are available in the same attributes, but prefixed with "sign_" instead of "bankid_".

Production Configuration

Refer to https://www.bankid.com/bankid-i-dina-tjanster/rp-info for details on URLs and certificates to use for production.

Testing

Ceptor comes with test certificates (see the example configuration above) downloaded from https://www.bankid.com 

To obtain devices to test with, you can e.g. configure the BankID iOS App with a test URL - see details here: https://www.bankid.com/bankid-i-dina-tjanster/rp-info

At bankid.com, you can also find information about how to create additional test users.

© Ceptor ApS. All Rights Reserved.