SMS / Text OTP
Purpose
Generating One Time PIN/Password (OTP) and send them to end users using SMS/Text messages, as well as verifying the entered code. This enables 2-factor authentication.
Features
- OTP code generation
- OTP code validation
- Send SMS using CPSMS provider
- Send SMS using Unwire provider
Overview
The SMS Authentication plugin exists in 2 variants;
- dk.itp.security.authentication.sms.SMSAuthenticationPlugin - an abstract class which contains all the logic for issuing OTPs and validating them
- dk.itp.portalprotect.useradmin.server.SMSUAAuthenticationPlugin - a concrete implementation which retrieves and validates challenges registered in the Ceptor User Administration Server. This implementation will also use the registered mobile number of the user to send the SMS/Text message
Configuration
This is the configuration for dk.itp.security.authentication.sms.SMSAuthenticationPlugin which is handles all the details.
Property | Value | Description |
---|---|---|
sms.httpProxyHost | <hostname> Default: blank | Hostname of HTTP proxy to use when contacting SMS Gateway, leave blank to skip proxy. |
sms.httpProxyPort | <tcp port> Default: 8080 | TCP port number for proxy server. |
sms.httpProxyUser | <userid> Default: blank | If proxy is enabled, this is the userid to send, if specified |
sms.httpProxyPassword | <password> Default: blank | If proxy is enabled, and userid is specified, this is the password to send to the proxy server. |
sms.httpNoProxyFor | <pattern> No default | Hostnames/IPs matching this pattern will not be proxied |
sms.from | <string> Default: PP OTP | Name or phone number that the SMS/Text message will appear to be sent from. |
sms.otplength | <integer> Default: 6 | Number of characters in the generated OTP code |
sms.numbersonly | true/false Default: true | If true, generated OTP only contains numbers - if false, it contains letters too. |
sms.flashsms | true/false Default: true | If true, the SMS/Text message is sent as flash SMS - meaning it will popup on the phone, and disappear when the user dismisses it - it will also not show up in the SMS history. |
sms.verifysslhostname | true/false Default: true | Set to false to disable SSL hostname verification - when true, it checks that the hostname matches what is specified in the SSL server certificate. |
sms.verifysslservercert | true/false Default: true | Set to false to disable verification of the SSL server certificate |
sms.provider | cpssm/unwire | Name of the SMS provider to use, must be cpsms or unwire An additional value of "locallogging" can be set to provide the OTP codes to the Ceptor log files. |
When SMS provider is set to cpsms | ||
sms.server | URL Default: https://www.cpsms.dk | URL (scheme and hostname) of the SMS gateway server |
sms.username | <userid> | Userid to send to the SMS gateway |
sms.password | <password> | Password for the SMS gateway |
sms.apikey | <string> | API Key to use instead of the userid/password when calling the gateway. |
When SMS provider is set to unwire | ||
sms.server | URL Default: https://gw.unwire.com | URL (scheme and hostname) of the SMS gateway server |
sms.username | <userid> | Userid to send to the SMS gateway |
sms.password | <password> | Password for the SMS gateway |
sms.smsc | <string> Default: dk.tdc | Company to route the SMS through Refer to unwire for details. |
sms.appnr | <number> Default: 1231 | Number within the routing company to use for sending the SMS Refer to unwire for details. |
sms.mediacode | <string> Default: PortalProtectOTP | Mediacode to use Refer to unwire for details. |
When using the version of the SMS authentication plugin that uses the useradmin database; dk.itp.portalprotect.useradmin.server.SMSUAAuthenticationPlugin the following additional configuration properties exist:
Property | Value | Description |
---|---|---|
useradminservers | <url> Default: localhost:15000 | URL to useradmin server |
ua_userid | <userid> | Userid to use when authenticating to useradmin server |
ua_password | <password> | Password to use when authenticating to useradmin server |
useridpassword.autounlockminutes | <value in minutes> Default: 0 | If nonzero, and user was automatically locked due to too many failed password attempts, he will automatically be unlocked after the specified number of minutes. |
useridpassword.maximuminvalidpasswordattempts | <number> Default: 0 | If nonzero, and if invalid login attempts reaches this limit, the user is automatically locked. |
Implementation in Your Custom Authentication Plugins
This is an example of an implementation of your own authentication plugin, which inherits from the abstract one.
Please note that this example just covers
package dk.portalprotect.nodb.plugins; import java.io.IOException; import java.net.MalformedURLException; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import dk.itp.security.authentication.sms.SMSAuthenticationPlugin; import dk.itp.security.passticket.PTException; import dk.itp.security.passticket.User; import dk.itp.security.passticket.server.AuthErrorCodes; /** * Examplle authentication plugin, used to demonstrate authenticating using SMS. * * @author Kim Rasmussen * * <pre> * Ceptor - http://ceptor.io * * This source code is confidential. * </pre> */ public class SMSAuthenticationExample extends SMSAuthenticationPlugin { static Logger log = LoggerFactory.getLogger(SMSAuthenticationExample.class.getName()); static final int DEFAULT_AUTHLEVEL = 10; /** * @see dk.itp.security.authentication.IAuthenticationPlugin#getAuthenticationLevel() */ public int getAuthenticationLevel() { return DEFAULT_AUTHLEVEL; } /** * @see dk.itp.security.authentication.IAuthenticationPlugin#login(User, String, Object, Object) */ public void login(User user, String userid, Object credentials) throws PTException { if (credentials instanceof String) { user.removeStateObject("SMS_OTP"); user.removeStateObject("SMS_USERID"); // Lookup userid in user repository, verify password // In this NON-PRODUCTION example, we assume the userid is the phone number, and the password is "password" if (!credentials.equals("password")) { throw new PTException("Invalid password", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "Invalid credentials"); } // If we get here, the password is ok // For now, use the userid as phone number - in a real authentication plugin, look it up somewhere else. String mobile = userid; mobile = mobile == null ? "" : mobile.trim(); if (mobile.startsWith("+")) mobile = mobile.substring(1); int ofs = mobile.indexOf(' '); while(ofs >= 0) { mobile = mobile.substring(0,ofs) + mobile.substring(ofs+1); ofs = mobile.indexOf(' '); } if (mobile == null || mobile.length() == 0) throw new PTException("No mobile number on record, cannot send PIN via SMS", AuthErrorCodes.ERROR_NOMOBILEPHONE, "No mobile number, cannot send SMS"); // If 8 chars, assume danish and add country code 45 for Denmark if (mobile.length() == 8) mobile = "45"+mobile; // Generate an OTP, store it in the session, and throw an exception telling the application we need the OTP. String otp = generateOTP(); user.setStateObject("SMS_OTP", otp); user.setStateObject("SMS_USERID", userid); try { if (!sendSMS(mobile, "PIN:\n"+otp)) throw new PTException("Unable to send SMS to " + mobile, AuthErrorCodes.ERROR_NOMOBILEPHONE, "Problem with mobile, cannot send SMS"); } catch (MalformedURLException e) { throw new PTException("Unable to send SMS to " + mobile, AuthErrorCodes.ERROR_NOMOBILEPHONE, "Configuration problem - invalid SMS configuration", e); } catch (IOException e) { throw new PTException("Unable to send SMS to " + mobile, AuthErrorCodes.ERROR_NOMOBILEPHONE, "Problem contacting SMS Gateway", e); } log.info("Logon with userid/password OK - SMS sent to " + mobile + ", need relogin with pincode"); throw new PTException("Need OTP pincode", AuthErrorCodes.ERROR_NEED_OTP, "Need OTP code"); } else if (credentials instanceof String[]) { // String[] means we are called with the one-time password. userid = (String) user.getStateObject("SMS_USERID"); String otp = (String)user.getStateObject("SMS_OTP"); if (userid == null || otp == null) { throw new PTException("Problem logging in, no userid present or timeout", AuthErrorCodes.ERROR_GENERALERROR, "No previous userid/password login or timeout"); } // Remove allows only one try - keep it to allow multiple attempts at entering the correct PIN user.removeStateObject("SMS_OTP"); user.removeStateObject("SMS_USERID"); if (!((String[])credentials)[0].equalsIgnoreCase(otp)) { throw new PTException("Invalid OTP PIN", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "Invalid PIN code"); } user.userid = userid; user.isLoggedOn = true; // Fill remaining information onto session } else { log.error("Cannot login, unsupported type of credentials"); throw new PTException("Unsupported type of credentials - cannot login", AuthErrorCodes.ERROR_GENERALERROR, "Unsupported type of credentials"); } } /** * @see dk.itp.security.authentication.IAuthenticationPlugin#setConfiguration(Properties) */ public void setConfiguration(Properties props) { super.setConfiguration(props); log.error("Using Example SMS Plugin with hardcoded credentials - NOT FOR PRODUCTION"); } }
© Ceptor ApS. All Rights Reserved.