Ch 22 Google Auth - {"error":true,"reason":"Value required for key 'access_token'."}

I am getting the following error at the final Run in the chapter.

{"error":true,"reason":"Value required for key 'access_token'."}

I tried everything I could think of in my own code. Then I loaded up the starter project for chapter 23, and got the same results…

Not sure what else to try on this one? I did a swift package update and then a vapor build. Using latest greatest public Xcode version.

@viper827 what request/point in the login with Google flow are you getting that error? I’ve just tried with GitHub - raywenderlich/vapor-til: The TIL Application for the Vapor book and it works

So it lets me click the button, I get taken to the google page to pick the account I wish to use, and then once it redirects me back to my page I get the error.

Can you show your ImperialController.swift? It’s like something is out of whack, especially as I can’t reproduce it :thinking:

import Vapor
import Imperial
import Authentication

struct ImperialController: RouteCollection {
  func boot(router: Router) throws {
guard let googleCallbackURL = Environment.get("GOOGLE_CALLBACK_URL") else {
  fatalError("Callback URL not set")
}
try router.oAuth(from: Google.self, authenticate: "login-google", callback: googleCallbackURL,
                 scope: ["profile", "email"], completion: processGoogleLogin)
}

func processGoogleLogin(request: Request, token: String) throws -> Future<ResponseEncodable> {
return try Google.getUser(on: request).flatMap(to: ResponseEncodable.self) { userInfo in
  return User.query(on: request).filter(\.username == userInfo.email)
             .first().flatMap(to: ResponseEncodable.self) { foundUser in
    guard let existingUser = foundUser else {
      let user = User(name: userInfo.name, username: userInfo.email, password: UUID().uuidString)
      return user.save(on: request).map(to: ResponseEncodable.self) { user in
        try request.authenticateSession(user)
        return request.redirect(to: "/")
      }
    }
    try request.authenticateSession(existingUser)
    return request.future(request.redirect(to: "/"))
  }
}
}
}

struct GoogleUserInfo: Content {
let email: String
let name: String
}

extension Google {
static func getUser(on request: Request) throws -> Future<GoogleUserInfo> {
var headers = HTTPHeaders()
headers.bearerAuthorization = try BearerAuthorization(token: request.accessToken())

let googleAPIURL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
return try request.client().get(googleAPIURL, headers: headers).map(to: GoogleUserInfo.self) { response in
  guard response.http.status == .ok else {
    if response.http.status == .unauthorized {
      throw Abort.redirect(to: "/login-google")
    } else {
      throw Abort(.internalServerError)
    }
  }
  return try response.content.syncDecode(GoogleUserInfo.self)
}
}
}

I’m using the completed file that provided with the book.

@viper827 if you breakpoint I’m assuming it’s not hitting your code and is failing inside Imeperial? Does it work if you try with the whole Vapor TIL project from GH?

@viper827 Do you still have issues with this?

I have exactly the same problem:

In Imperial this error is triggered:

public func fetchToken(from request: Request) throws → EventLoopFuture {
let code: String
if let queryCode: String = try request.query.get(at: codeKey) {
code = queryCode
} else if let error: String = try request.query.get(at: errorKey) {
throw Abort(.badRequest, reason: error)
} else {
throw Abort(.badRequest, reason: “Missing ‘code’ key in URL query”)
}

    let body = callbackBody(with: code)
    let url = URI(string: accessTokenURL)
    
    return body.encodeResponse(for: request)
        .map { $0.body.buffer }
        .flatMap { buffer in
            return request.client.post(url, headers: self.callbackHeaders) { $0.body = buffer }
        }.flatMapThrowing { response in
            return try response.content.get(String.self, at: ["access_token"])
        }
}

I also use exactly the same code from the book:

import ImperialGoogle
import Vapor
import Fluent

struct ImperialRouter: RouteCollection {

func boot(routes: RoutesBuilder) throws {

 guard let googleCallbackURL = Environment.get("GOOGLE_CALLBACK_URL")
 else { fatalError("Google callback URL not set") }

 try routes.oAuth(from: Google.self,
                  authenticate: "login-google",
                  callback: googleCallbackURL,
                  scope: ["profile", "email"],
                  completion: processGoogleLogin
 )

}

func processGoogleLogin(request: Request, token: String) throws → EventLoopFuture {

try Google
    .getUser(on: request)
    .flatMap { userInfo in
       Author
          .query(on: request.db)
          .filter(\.$username == userInfo.email)
          .first()
          .flatMap { foundUser in

             guard let existingUser = foundUser
             else {
                let user = Author(first_name: "",
                                  last_name: userInfo.name,
                                  username: userInfo.email,
                                  password: UUID().uuidString,
                                  role: .VIEWER
                )
                return user
                .save(on: request.db)
                .map {
                   request.session.authenticate(user)
                   return request.redirect(to: "/")
                }
             }
             request.session.authenticate(existingUser)
             return request.eventLoop.future(request.redirect(to: "/"))
          }
    }

}

}

struct GoogleUserInfo: Content {
let email: String
let name: String
}

extension Google {

static func getUser(on request: Request) throws → EventLoopFuture {

 var headers = HTTPHeaders()
 headers.bearerAuthorization = try BearerAuthorization(token: request.accessToken())

 let googleAPIURL: URI = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"

 return request.client.get(googleAPIURL, headers: headers).flatMapThrowing { response in

 guard response.status == .ok
 else { if response.status == .unauthorized {
               throw Abort.redirect(to: "/login-google")
      } else { throw Abort(.internalServerError) }
 }

  return try response.content.decode(GoogleUserInfo.self)
}

}

}

This is the error message:

Bildschirmfoto 2022-03-30 um 19.28.33
Bildschirmfoto 2022-03-30 um 19.28.26

(Postet it also on discord but then I saw that other people had exactly the same issue. Therefore I added it here in case somebody could solve this issue already.)

This error happens on the iOS client when the server is not running. Once I re-ran the server (which I thought was running, but stopped working after my machine was slept and un slept several times) it all started working again.