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 |
---|---|---|
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 |
---|---|
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.