Getting ID token from google sign-in - kotlin

I am trying to use http4k's built in oauth module to implement Google sign-in in my backend app.
Here is what I have so far:
val googleClientId = "<GoogleClientID>"
val googleClientSecret = "<GoogleClientSecret>"
// this is a test implementation of the OAuthPersistence interface, which should be
// implemented by application developers
val oAuthPersistence = InsecureCookieBasedOAuthPersistence("Google")
// pre-defined configuration exist for common OAuth providers
val oauthProvider = OAuthProvider.google(
JavaHttpClient(),
Credentials(googleClientId, googleClientSecret),
Uri.of("http://localhost:9000/oauth/callback"),
oAuthPersistence
)
val app: HttpHandler = routes(
"/oauth" bind routes(
"/" bind GET to oauthProvider.authFilter.then {
val user = contextFn(it)
Response(OK).body("authenticated!")
},
"/callback" bind GET to oauthProvider.callback
)
app.asServer(SunHttp(9000)).start()
This lets me go to http://localhost:9000/oauth and I can sign-in to my google account. Cool!
However, after the redirect, I go to the following function contextFn, which looks like this atm:
val transport = NetHttpTransport()
val jsonFactory = GsonFactory.getDefaultInstance()
val verifier = GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(listOf(googleClientId))
.build()
fun contextFn(request: Request): Principal {
// TODO: get the id token somehow, but the request header only contains the following in cookie:
// - GoogleCsrf
// - GoogleAccessToken
// - GoogleOriginalUri
val idTokenString = ""
val idToken: GoogleIdToken = verifier.verify(idTokenString)
val payload: GoogleIdToken.Payload = idToken.payload
// Print user identifier
val userId: String = payload.subject
println("User ID: $userId")
// Get profile information from payload
val email: String = payload.email
val emailVerified: Boolean = payload.emailVerified
val name = payload["name"]
return GoogleUser(email)
}
How can i get the id token? Currently I am getting the access token from google.

Can you try to add this scope(openid)?
I am not sure listOf or addScope support in http4k
But it missed openid scope.
val oauthProvider = OAuthProvider.google(
JavaHttpClient(),
Credentials(googleClientId, googleClientSecret),
Uri.of("http://localhost:9000/oauth/callback"),
listOf("openidScope"),
oAuthPersistence
)
oauthProvider.addScope('openid');

Related

PayPal android integration infinite loading on payment

I am trying to implement paypal payment, using their documentation on sandbox accounts. So, my code looks like this:
fun setupPayPal(
payPalButton: PayPalButton, bottomSheetDialog: BottomSheetDialog,
orderRequest: OrderRequest
) {
val config = CheckoutConfig(
application = fragmentActivity.application,
clientId = Config.PAYPAL_CLIENT_ID,
environment = Environment.SANDBOX,
returnUrl = "com.example.shopkotlin://paypalpay",
currencyCode = CurrencyCode.USD,
userAction = UserAction.PAY_NOW,
settingsConfig = SettingsConfig(
loggingEnabled = true
))
PayPalCheckout.setConfig(config)
payPalButton.setup(
createOrder =
CreateOrder { createOrderActions ->
val order =
Order(
intent = OrderIntent.CAPTURE,
appContext = AppContext(userAction = UserAction.PAY_NOW),
purchaseUnitList =
listOf(
PurchaseUnit(
amount =
Amount(currencyCode = CurrencyCode.USD, value = "10.00")
)
),
processingInstruction = ProcessingInstruction.ORDER_COMPLETE_ON_PAYMENT_APPROVAL
)
createOrderActions.create(order)
},
onApprove =
OnApprove { approval ->
approval.orderActions.capture {
orderRequest.paymentMethod = "PayPal"
orderViewModel!!.createOrder(token, orderRequest)
bottomSheetDialog.cancel()
}
},
onError = OnError { errorInfo ->
println("error $errorInfo")
}
)
}
The problem is that it's starts properly, i am logging in, however it stucks and infinite loading at sandbox.paypal.com , i am using Kotlin, on my Java application the same code works as charm, but this one does not, does someone knows why? I looked into logs, no error so far.
Fixed by changing the return URL correctly. In paypal.developer we should set the URL how it is in my code, however we must make sure that URL matches, check in build.gradle (module).

Gmail API returns "Delegation denied for my.email#email.com" when setting a different email as recipient

I am using the Gmail API to create drafts.
When I create a draft message where the recipient is my own email (the one that generates the credential), everything works fine but when I try to use a different email, the following message is printed:
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Delegation denied for my.email.here#gmail.com",
"reason" : "forbidden"
} ],
"message" : "Delegation denied for my.email.here#gmail.com",
"status" : "PERMISSION_DENIED"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
That's how I'm assembling my MimeMessage:
val props = Properties()
val session = Session.getDefaultInstance(props, null)
val message = MimeMessage(session)
message.setFrom(message.sender)
message.addRecipient(JavaxMessage.RecipientType.TO, InternetAddress("different.email.here#gmail.com"))
message.subject = subject
The scopes I am using:
// "https://www.googleapis.com/auth/gmail.compose"
GmailScopes.GMAIL_COMPOSE
I've tried a lot of stuff to make it work, but I didn't have any success.
Delegation denied for my.email.here#gmail.com
This email appears to be a standard gmail email address. You appear to be trying to delegate a service account to a user with a standard gmail email address.
Service accounts only work with google workspace email accounts. You need to set up domain wide delegation to the serveries account and then you can set the delegation user.
If you want to use a standard gmail account you will need to authorize the user using Oauth2.
private fun getCredentials(httpTransport: NetHttpTransport): Credential? {
val inputStream = File("credentials.json").inputStream()
val clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))
val flow = GoogleAuthorizationCodeFlow.Builder(httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build()
val receiver = LocalServerReceiver.Builder().setPort(8888).build()
return AuthorizationCodeInstalledApp(flow, receiver).authorize("user")
}
public static MimeMessage createEmail(String to,
String from,
String subject,
String bodyText)
throws MessagingException {
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
MimeMessage email = new MimeMessage(session);
email.setFrom(new InternetAddress(from));
email.addRecipient(javax.mail.Message.RecipientType.TO,
new InternetAddress(to));
email.setSubject(subject);
email.setText(bodyText);
return email;
}
Sending
Thank you #DaImTo for your reply and all the info provided.
Based on the info provided, to those who have the same problem as I do, I've found a nice workaround:
set the "TO" recipient to the user's email;
add a "BCC" recipient to the actual recipient email.
Here's the code to demonstrate it:
val meProfile = gmail.users().getProfile("me").execute()
val toRecipient = InternetAddress(meProfile.emailAddress)
val ccRecipient = InternetAddress(to)
val props = Properties()
val session = Session.getDefaultInstance(props, null)
val message = MimeMessage(session)
message.setFrom(message.sender)
// here's the magic :D
message.addRecipient(JavaxMessage.RecipientType.TO, toRecipient)
message.addRecipient(JavaxMessage.RecipientType.BCC, ccRecipient)
message.subject = subject

How to dynamically choose which authentication method is used in Ktor?

I implemented google sign-in in my application like so:
fun Application.module(testing: Boolean = false) {
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
val jwtIssuer = environment.config.property("jwt.domain").getString()
val jwtAudience = environment.config.property("jwt.audience").getString()
val jwtRealm = environment.config.property("jwt.realm").getString()
val jwkProvider = JwkProviderBuilder(URL("https://www.googleapis.com/oauth2/v3/certs"))
.cached(10, 24, TimeUnit.HOURS)
.rateLimited(10, 1, TimeUnit.MINUTES)
.build()
install(Authentication) {
jwt {
verifier(jwkProvider) {
withIssuer(jwtIssuer)
withAudience(jwtAudience)
}
realm = jwtRealm
validate { credentials ->
if (credentials.payload.audience.contains(jwtAudience))
JWTPrincipal(credentials.payload)
else
null
}
}
}
routing {
authenticate {
post("/token-sign-in") {
val payload = call.principal<JWTPrincipal>()?.payload ?: error("JWTPrincipal not found")
call.respond(
UserWire(
id = payload.subject,
email = payload.getClaim("email").asString(),
name = payload.getClaim("name").asString(),
profilePictureUrl = payload.getClaim("picture").asString()
)
)
}
}
}
}
I want to authenticate the user every single time they access one of the routes, but I want to have both google and firebase-auth login as an option. The thing is that they require different methods to check the authenticity of the given token, hence I need two authentication methods.
I was thinking of including an "AuthenticationProvider: "Google|Firebase"" in the header of the call, and according to its value, I would decide which authentication method should be called.
So something like this:
fun Application.module(testing: Boolean = false) {
install(Authentication) {
jwt("google") {
// verify google sign in token
}
jwt("firebase") {
// verify firebase token
}
firebaseOrGoogle("firebaseOrGoogle") {
// check header value for auth provider
// verify token with either "firebase" or "google" auth methods
}
}
routing {
authenticate("firebaseOrGoogle") {
post("/token-sign-in") {
// ...
}
get("/transactions") {
// ...
}
}
}
}
Is this at all possible?
If this is possible please could you provide some code as to how to dynamically decide which authentication method should be called?
As an alternative solution, you can configure an authentication feature to try proving the identity of a user by both methods. The first successful check wins. To do that just pass those two configuration names to the authenticate method:
routing {
authenticate("google", "firebase") {
post("/token-sign-in") {
// ...
}
get("/transactions") {
// ...
}
}
}
The order of arguments determines what check comes first.

In Ktor how do I get the sessionId within a get() {}?

install(Sessions) {
header<MySession>("MY_SESSION", SessionStorageMemory())
}
get("/session/increment") {
val session = call.sessions.get<MySession>() ?: throw AuthorizationException()
call.sessions.set(session.copy(count = session.count + 1))
// insert code here to get sessionId ?
call.respondText("Counter is ${session.count}. Refresh to increment.")
}
I've been trying to get it out of the attributes but it seems the framework has made all those data structures private to prevent me from getting the sessionId and have no working solution yet.
val attributeKey = call.attributes.allKeys.map{
val x = it as AttributeKey<Any>
call.attributes.get(x)
}
// AttributeKey<SessionData>("SessionKey")
SessionData is private so I can't get access to data structure that holds sessionId

How to define SSLContext with Spray https client?

I want to post http requests to a secured server with a given ca cert.
I'm using Spray 1.3.1, the code looks something like this:
val is = this.getClass().getResourceAsStream("/cacert.crt")
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val caCert: X509Certificate = cf.generateCertificate(is).asInstanceOf[X509Certificate];
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
val ks: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
implicit val sslContext: SSLContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
implicit val timeout: Timeout = Timeout(15.seconds)
import spray.httpx.RequestBuilding._
val respFuture = (IO(Http) ? Post( uri=Uri(url), content="my content")).mapTo[HttpResponse]
The problem is that the defined implicit SSLContext is not taken and I'm getting: "unable to find valid certification path to requested target" on runtime.
How can I define a SSLContext to be used with spray client?
I use the following to define an SSLContext in spray. In my case, I'm using a very permissive context that does not validate the remote server's certificate. Based on the first solution in this post - works for me.
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.{SSLContext, X509TrustManager, TrustManager}
import akka.actor.ActorRef
import akka.io.IO
import akka.util.Timeout
import spray.can.Http
import scala.concurrent.Future
trait HttpClient {
/** For the HostConnectorSetup ask operation. */
implicit val ImplicitPoolSetupTimeout: Timeout = 30 seconds
val hostName: String
val hostPort: Int
implicit val sslContext = {
/** Create a trust manager that does not validate certificate chains. */
val permissiveTrustManager: TrustManager = new X509TrustManager() {
override def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {
}
override def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {
}
override def getAcceptedIssuers(): Array[X509Certificate] = {
null
}
}
val initTrustManagers = Array(permissiveTrustManager)
val ctx = SSLContext.getInstance("TLS")
ctx.init(null, initTrustManagers, new SecureRandom())
ctx
}
def initClientPool(): Future[ActorRef] = {
val hostPoolFuture = for {
Http.HostConnectorInfo(connector, _) <- IO(Http) ? Http.HostConnectorSetup(hostName, port = hostPort,
sslEncryption = true)
} yield connector
}
}
I came up with this replacement for sendReceive which allows passing a custom SSLContext (as an implicit)
def mySendReceive( request: HttpRequest )( implicit uri: spray.http.Uri, ec: ExecutionContext, futureTimeout: Timeout = 60.seconds, sslContext: SSLContext = SSLContext.getDefault): Future[ HttpResponse ] = {
implicit val clientSSLEngineProvider = ClientSSLEngineProvider { _ =>
val engine = sslContext.createSSLEngine( )
engine.setUseClientMode( true )
engine
}
for {
Http.HostConnectorInfo( connector, _ ) <- IO( Http ) ? Http.HostConnectorSetup( uri.authority.host.address, port = uri.authority.port, sslEncryption = true )
response <- connector ? request
} yield response match {
case x: HttpResponse ⇒ x
case x: HttpResponsePart ⇒ sys.error( "sendReceive doesn't support chunked responses, try sendTo instead" )
case x: Http.ConnectionClosed ⇒ sys.error( "Connection closed before reception of response: " + x )
case x ⇒ sys.error( "Unexpected response from HTTP transport: " + x )
}
}
Then use it as "usual" (almost see below):
val pipeline: HttpRequest => Future[ HttpResponse ] = mySendReceive
pipeline( Get( uri ) ) map processResponse
There are a couple of things I really do not like though:
it is a hack. I would expect spray-client to allow support of a custom SSLContext natively. These are very useful during dev and test, to force custom TrustManagers typically
there is an implicit uri: spray.http.Uri parameter to avoid hard coding of the host and port on the connector. So uri must be declared implicit.
Any improvement to this code or even better, a patch to spray-client, is most welcome (externalization of the creation of the SSLEngine being an obvious one)
The shortest I've gotten to work is this:
IO(Http) ! HostConnectorSetup(host = Conf.base.getHost, port = 443, sslEncryption = true)
i.e. what's in #reed-sandberg's answer, but no ask pattern seems to be needed. I don't pass a connection parameter to sendReceive but instead:
// `host` is the host part of the service
//
def addHost = { req: HttpRequest => req.withEffectiveUri(true, Host(host, 443)) }
val pipeline: HttpRequest => Future[Seq[PartitionInfo]] = (
addHost
~> sendReceive
~> unmarshal[...]
)
This seems to work, but I would naturally be interested to hear if there are downsides to this approach.
I agree with all spray-client SSL support criticism. It's awkward that something like this is so hard. I probably spent 2 days on it, merging data from various sources (SO, spray documentation, mailing list).