Hello! I'm currently working on integrating a user authentication flow using SotoCognito and Vapor's SessionAuthenticators to persist authentication. I have a breakpoint in authenticate in UserSessionAuthenticator, but my requests never pass through there, so it's always redirecting to the login page. What might be happening here?
Here's a snippet of the code:
func routes(_ app: Application) throws {
let authenticated = app.routes.grouped([app.sessions.middleware, UserSessionAuthenticator()])
let portalRedirect = authenticated.grouped(AuthenticatedUser.redirectMiddleware(path: "login"))
authenticated.post("login", use: login)
authenticated.get("login") { req async throws in
try await req.view.render("login", ["title": "Login"])
}
portalRedirect.get("portal") { req async throws in
try await req.view.render("portal", ["title": "Portal"])
}
}
func login(_ req: Request) async throws -> Response {
let user = try req.content.decode(User.self)
let response = try await req.application.cognito.authenticatable.authenticate(username: user.email, password: user.password, context: req)
switch response {
case .authenticated(let authenticatedResponse):
req.auth.login(AuthenticatedUser(sessionID: authenticatedResponse.accessToken!))
default:
...
}
throw Abort.redirect(to: "portal")
}
struct User: Content {
var email: String
var password: String
}
struct AuthenticatedUser: SessionAuthenticatable {
var sessionID: String
}
struct UserSessionAuthenticator: AsyncSessionAuthenticator {
typealias User = AuthenticatedUser
func authenticate(sessionID: String, for request: Vapor.Request) async throws {
do {
// TODO: handle response, this is to check if token is still valid
let response = try await request.application.cognito.authenticatable.authenticate(accessToken: sessionID, on: request.eventLoop)
request.auth.login(User(sessionID: sessionID))
} catch let error as SotoCognitoError {
...
}
}
}
I might be wrong, but I think this is happening because nothing is persisted in the session on the client yet (i.e. the vapor_session cookie is not set). From the docs:
If the authentication has not been persisted in the session yet, the request will be forwarded to the next authenticator.
Can you confirm if this cookie is set on the request?
Yes, I do see the vapor_session cookie upon getting the login page (I've since changed authenticated.get("login") to app.get("login") so now the cookie is only generated when the user submits the login form).
Ahh, I understand! I added that in the login route but the behavior remains the same (portal redirects to login and not hitting the authenticate breakpoint in UserSessionAuthenticator).
It only hits the redirect middleware, but never the user session authenticator. In login, the request goes through the authenticated case, then redirects to portal. The get request to portal then hits the redirect middleware and the login page is displayed again.
Specifically:
[ INFO ] POST /login [request-id: 38BCDF6E-6238-43A3-9D97-7443606139FB]
[ WARNING ] Abort.303: See Other [request-id: 38BCDF6E-6238-43A3-9D97-7443606139FB]
[ INFO ] GET /portal [request-id: 1D2763DC-03E1-4B66-A564-961A2E8D9899]
[ INFO ] GET /login [request-id: 80AE9F2A-3B25-4C52-AA5F-A1E4BC8BA6A9]
You'll need to put a breakpoint inside the conformance of AsyncSessionAuthenticator to AsyncMiddleware to see why it's not even running through the authenticator (e.g. it can't find a session ID), inspect the request and then work backwards from there