MOB-42 Fixed token authentication issues (wrong library version, cache getting recreated every request, origin in wrong form)

This commit is contained in:
TanelOrumaa 2021-11-11 21:47:27 +02:00
parent 9b0cb1a22d
commit 636beeb7f3
15 changed files with 64 additions and 21 deletions

View File

@ -90,7 +90,7 @@ class AuthFragment : Fragment() {
if (args.auth) {
val jws = Authenticator(comms).authenticate(
intentParameters.challenge,
intentParameters.authUrl,
intentParameters.origin,
viewModel.userPin
)
intentParameters.setToken(jws)

View File

@ -14,6 +14,7 @@ import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
import java.lang.Exception
import java.net.URLDecoder
/**
* HomeFragment is only shown to the user when then the user launches the application. When the application
@ -30,6 +31,7 @@ class HomeFragment : Fragment() {
private var binding: FragmentHomeBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -56,11 +58,16 @@ class HomeFragment : Fragment() {
// We use !! because we want an exception when something is not right.
intentParams.setChallenge(requireActivity().intent.getStringExtra("challenge")!!)
intentParams.setAuthUrl(requireActivity().intent.getStringExtra("authUrl")!!)
intentParams.setOrigin(requireActivity().intent.getStringExtra("originUrl")!!)
} else { //Website
// Currently the test website won't send the authUrl parameter
//Log.i("intentDebugging", requireActivity().intent.data.toString())
intentParams.setChallenge(requireActivity().intent.data!!.getQueryParameter("challenge")!!)
var challenge = requireActivity().intent.data!!.getQueryParameter("challenge")!!
// TODO: Since due to encoding plus gets converted to space, temporary solution is to replace it back.
challenge = challenge.replace(" ", "+")
intentParams.setChallenge(challenge)
intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!)
intentParams.setOrigin(requireActivity().intent.data!!.getQueryParameter("originUrl")!!)
}
} catch (e: Exception) {
// There was a problem with parameters, which means that authentication is not possible.

View File

@ -67,7 +67,7 @@ class ResultFragment : Fragment() {
Ion.getDefault(activity).getConscryptMiddleware().enable(false)
Ion.with(activity)
.load("https://6bb0-85-253-195-252.ngrok.io/auth/authentication")
.load(paramsModel.origin + paramsModel.authUrl)
.setJsonObjectBody(json)
.asJsonObject()
.setCallback { e, result ->

View File

@ -8,7 +8,7 @@ import java.security.MessageDigest
import java.time.LocalDateTime
import java.time.ZoneOffset
class Authenticator(val comms : Comms) {
class Authenticator(val comms: Comms) {
val type = "JWT"
val algorithm = "ES384"
@ -36,7 +36,7 @@ class Authenticator(val comms : Comms) {
// Get header and claims.
val header = """{"typ":"$type","alg":"$algorithm","x5c":["$base64cert"]}"""
val claims =
"""{"iat":"$epoch","exp":"$exp","aud":"$originUrl","iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}"""
"""{"iat":"$epoch","exp":"$exp","aud":["$originUrl"],"iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}"""
val jwt = base64Encode(header.toByteArray(Charsets.UTF_8)) + "." + base64Encode(
claims.toByteArray(Charsets.UTF_8)
@ -51,7 +51,7 @@ class Authenticator(val comms : Comms) {
return jwt + "." + base64Encode(signed)
}
fun base64Encode(bytes: ByteArray) : String? {
fun base64Encode(bytes: ByteArray): String? {
val encoded = java.util.Base64.getUrlEncoder().encodeToString(bytes)
return encoded.replace("=", "")
}

View File

@ -13,6 +13,9 @@ class ParametersViewModel: ViewModel() {
private var _token: String = ""
val token get() = _token
private var _origin: String = ""
val origin get() = _origin
fun setChallenge(newChallenge: String) {
_challenge = newChallenge
}
@ -24,4 +27,8 @@ class ParametersViewModel: ViewModel() {
fun setToken(newToken: String) {
_token = newToken
}
fun setOrigin(newOrigin: String) {
_origin = newOrigin
}
}

View File

@ -42,6 +42,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency>
<groupId>org.webeid.security</groupId>
<artifactId>authtoken-validation</artifactId>

View File

@ -10,7 +10,7 @@ class ApplicationConfiguration {
// Endpoint for authentication
val AUTHENTICATION_ENDPOINT_URL = "/auth/authentication"
// URL for application. Use ngrok for HTTPS (or a tool of your own choice) and put the HTTPS link here.
val WEBSITE_ORIGIN_URL = "https://6bb0-85-253-195-252.ngrok.io"
val WEBSITE_ORIGIN_URL = "https://2c2c-85-253-195-252.ngrok.io"
}
}

View File

@ -1,6 +1,8 @@
package com.tarkvaratehnika.demobackend.config
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
@ -28,14 +30,25 @@ import javax.cache.configuration.MutableConfiguration
import javax.cache.expiry.CreatedExpiryPolicy
import javax.cache.expiry.Duration
import javax.cache.configuration.FactoryBuilder.factoryOf
@Configuration
class ValidationConfiguration {
private val LOG: Logger = LoggerFactory.getLogger(ValidationConfiguration::class.java)
private val NONCE_TTL_MINUTES: Long = 5
private val CACHE_NAME = "nonceCache"
private val CERTS_RESOURCE_PATH = "/certs/"
private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks"
private val TRUSTSTORE_PASSWORD = "changeit"
companion object {
const val ROLE_USER : String = "ROLE_USER"
}
init {
LOG.warn("Creating new ValidationConfiguration.")
}
@Bean
fun cacheManager(): CacheManager {
@ -47,7 +60,9 @@ class ValidationConfiguration {
val cacheManager: CacheManager = cacheManager()
var cache =
cacheManager.getCache<String?, ZonedDateTime?>(CACHE_NAME)
if (cache == null) {
LOG.warn("Creating new cache.")
cache = createNonceCache(cacheManager)
}
return cache
@ -65,7 +80,7 @@ class ValidationConfiguration {
val cacheConfig: CompleteConfiguration<String, ZonedDateTime> = MutableConfiguration<String, ZonedDateTime>()
.setTypes(String::class.java, ZonedDateTime::class.java)
.setExpiryPolicyFactory(
FactoryBuilder.factoryOf(
factoryOf(
CreatedExpiryPolicy(
Duration(
TimeUnit.MINUTES,

View File

@ -23,6 +23,9 @@
package com.tarkvaratehnika.demobackend.security
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
import com.tarkvaratehnika.demobackend.web.rest.AuthenticationController
import org.slf4j.LoggerFactory
import org.springframework.security.authentication.AuthenticationServiceException
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
@ -37,11 +40,11 @@ import java.security.cert.X509Certificate
@Component
class AuthTokenDTOAuthenticationProvider {
object AuthTokenDTOAuthenticationProvider {
private val LOG = LoggerFactory.getLogger(AuthTokenDTOAuthenticationProvider::class.java)
companion object {
const val ROLE_USER : String = "ROLE_USER"
}
private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER)
@ -52,7 +55,6 @@ class AuthTokenDTOAuthenticationProvider {
val authentication = auth as PreAuthenticatedAuthenticationToken
val token = (authentication.credentials as AuthTokenDTO).token
val challenge = (authentication.credentials as AuthTokenDTO).challenge
val authorities = arrayListOf<GrantedAuthority>()
authorities.add(USER_ROLE)

View File

@ -1,6 +1,6 @@
package com.tarkvaratehnika.demobackend.web
import com.tarkvaratehnika.demobackend.security.AuthTokenDTOAuthenticationProvider.Companion.ROLE_USER
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
import org.springframework.ui.Model

View File

@ -26,7 +26,7 @@ class AuthenticationController {
val auth = PreAuthenticatedAuthenticationToken(null, authToken)
// Return authentication object if success.
return AuthTokenDTOAuthenticationProvider().authenticate(auth)
return AuthTokenDTOAuthenticationProvider.authenticate(auth)
}
@GetMapping("authentication", produces = [MediaType.APPLICATION_JSON_VALUE])

View File

@ -24,6 +24,7 @@ package com.tarkvaratehnika.demobackend.web.rest
import com.tarkvaratehnika.demobackend.dto.ChallengeDto
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@ -33,10 +34,12 @@ import org.webeid.security.nonce.NonceGenerator
@RequestMapping("auth")
class ChallengeController (val nonceGenerator: NonceGenerator) {
private val LOG = LoggerFactory.getLogger(ChallengeController::class.java)
@GetMapping("challenge")
fun challenge(): ChallengeDto {
val challengeDto = ChallengeDto(nonceGenerator.generateAndStoreNonce())
LOG.warn(challengeDto.nonce)
// WebEidAuthentication.addAuth(challengeDto.nonce) // For testing.
return challengeDto
}

View File

@ -1,3 +1,7 @@
html {
font-size: 4vw;
}
.cont {
display: grid;
width: 80%;

View File

@ -11,7 +11,7 @@ function launchAuthApp(action) {
httpGetAsync(originUrl + challengeUrl, (body) => {
let data = JSON.parse(body);
let challenge = data.nonce;
let intent = createParametrizedIntentUrl(challenge, action); // TODO: Error handling.
let intent = createParametrizedIntentUrl(challenge, action, originUrl); // TODO: Error handling.
console.log(intent);
window.location.href = intent;
pollForAuth(POLLING_INTERVAL, challenge);
@ -20,8 +20,8 @@ function launchAuthApp(action) {
function pollForAuth(timeout, challenge) {
console.log("Polling for auth");
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + challenge;
let encodedChallenge = encodeURIComponent(challenge);
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + encodedChallenge;
let counter = 0;
let timer = setInterval(() => {
// Fetch authentication object.
@ -48,7 +48,7 @@ function createParametrizedIntentUrl(challenge, action) {
else if (challenge == null) {
console.error("Challenge missing, can't authenticate without it.")
} else {
return intentUrl + "?" + "action=" + action + "&challenge=" + challenge + "&authUrl=" + originUrl + authenticationRequestUrl;
return intentUrl + "?" + "action=" + action + "&challenge=" + encodeURIComponent(challenge) + "&authUrl=" + authenticationRequestUrl + "&originUrl=" + originUrl;
}
}

View File

@ -21,7 +21,7 @@
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Auth demo web application</a>
<a class="navbar-brand" href="#">Auth demo webapp</a>
</div>
</nav>
<div class="cont">
@ -29,7 +29,7 @@
website using your ID card by using the button below.</h4>
<h5>Make sure you've installed the authentication app from: <a
href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">GitHub</a></h5>
<button type="button" class="btn btn-secondary" id="loginButton" data-action="auth">Log in</button>
<button type="button" class="btn btn-lg btn-secondary" id="loginButton" data-action="auth">Log in</button>
<div class="alert alert-danger d-none" role="alert" id="loginErrorAlert">
Login failed. Refresh the page to try again.
</div>