Giter Club home page Giter Club logo

Comments (18)

czechboy0 avatar czechboy0 commented on July 17, 2024 3

This example should be a good blueprint for what you need 🙂

https://github.com/apple/swift-openapi-generator/tree/main/Examples/auth-client-middleware-example

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on July 17, 2024 3

Yes, you can make your middleware an actor, for example, or a class that you make Sendable and manually ensure safe access to internal mutable state. In this middleware stateful object, you can manage refreshing the credentials as needed, and always inject the current credentials in the intercept method.

That way, you can still create a single Client at the start, and reuse it even as the credentials change.

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on July 17, 2024 1

@Jonovono you could add the value to a TaskLocal value on the server, which allows you to access it from the handler. Or inject a token storage object both into the middleware and the object implementing APIProtocol on the server.

from swift-openapi-generator.

Jonovono avatar Jonovono commented on July 17, 2024 1

Thanks @czechboy0 , I have went the TaskLocal route and seems to be working good!

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on July 17, 2024 1

Yes, there are two solutions to this, one you can use now, and one tracked by this issue that aims to generate some type-safe representation of the authorization rules from the OpenAPI document.

The first solution is to create a Vapor AsyncMiddleware type and save whatever information you'd like to pass along into a task local value. Then in the handler (your type conforming to APIProtocol, you extract the task local value and use it).

Something like this should work:

import OpenAPIVapor
import Vapor
​
enum MyTaskLocal {
    @TaskLocal
    static var vaporValue: String? // Or whatever value you're extracting.
}struct TaskLocalVaporMiddleware: AsyncMiddleware {
    func respond(
        to request: Vapor.Request,
        chainingTo next: Vapor.AsyncResponder
    ) async throws -> Vapor.Response {
        try await MyTaskLocal.$vaporValue.withValue(request.<EXTRACT_VALUE_HERE>) {
            try await next.respond(to: request)
        }
    }
}struct Handler: APIProtocol {
    func test(_ input: Operations.test.Input) async throws -> Operations.test.Output {
        print("Task local from Vapor middleware: \(MyTaskLocal.vaporValue ?? "<nil>")")
        return .noContent(.init())
    }
}@main
struct Tool {
    static func main() async throws {
        let app = Application()
        app.middleware.use(TaskLocalVaporMiddleware())
        let transport = VaporTransport(routesBuilder: app)
        let handler = Handler()
        try handler.registerHandlers(on: transport)
        try app.run()
    }
}

One caveat, the last time I tested this, the middleware had to be the last one in the chain (otherwise it doesn't work), so add it right before you start the server. This allows you to pass values from the concrete Vapor.Request type all the way to your type-safe handlers.

from swift-openapi-generator.

marius-se avatar marius-se commented on July 17, 2024 1

@czechboy0 Thank you very much for your answer! While this does remove the additional route checking during runtime it is not very flexible. With each additional middleware this will require more symlinks and tags. Also it requires changing the OpenAPI yaml just to make something in Vapor work, which feels really wrong to me.

It is definitely a viable option for a small project, but for larger ones this will get out of control. I guess for now I'll stick to the additional path checking in the middleware!

Many thanks for you suggestions! 🙏🏼

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on July 17, 2024 1

@tib yup, that's how it's expected to be used 👍

from swift-openapi-generator.

Jonovono avatar Jonovono commented on July 17, 2024

@czechboy0 I'm able to get the client middleware to inject headers to the request, but whats the best way to extract that on the server?

What I have tried so far is having a server middleware that verifies the token:

    func intercept(
        _ request: Request,
        metadata: ServerRequestMetadata,
        operationID: String,
        next: (Request, ServerRequestMetadata) async throws -> Response
    ) async throws -> Response {
        if protectedOperations.contains(operationID) {
            if let authToken = request.headerFields.first(where: { $0.name == "Authorization" })?.value
            {

                if validateJWTToken(authToken) {
                    var newHeaderFields = request.headerFields
                    newHeaderFields.append(HeaderField(name: "uid", value: "the-user-id"))

                    let newRequest = Request(
                        path: request.path,
                        query: request.query,
                        method: request.method,
                        headerFields: newHeaderFields,
                        body: request.body
                    )
                    let response = try await next(newRequest, metadata)
                    return response

All that works great, but from the auth jwt token I extract a uid, i'd like to pass that along to the subsequent handlers. How could I achieve this?

In the code above, I tried adding it to the headers, but then in my handler I don't seem to see it:

    func authorizeUser(_ input: Operations.authorizeUser.Input) async throws -> Operations.authorizeUser.Output {
        print("INPUT", input.headers)
        return .ok(.init())
    }

Any ideas? I might be doing it completely wrong

from swift-openapi-generator.

mapedd avatar mapedd commented on July 17, 2024

Right now, for default handlers generated by OpenAPI for Vapor. there's no way to access request object to authorize it. Is there any solution that being worked on?

from swift-openapi-generator.

mapedd avatar mapedd commented on July 17, 2024

This looks great, thanks!

from swift-openapi-generator.

marius-se avatar marius-se commented on July 17, 2024

Something like this should work:
...

What's the go-to way (until we get proper support) for registering different middlewares for different routes? My current solution is to switch over request.route in TaskLocalVaporMiddleware.respond, but that seems far from ideal.

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on July 17, 2024

The ServerMiddleware protocol provides an operationId parameter, which you can use to change behavior based on the operation.

Alternatively, you can inspect the path, e.g. if your auth requirements can be derived from just some common path prefix.

I'd have to learn more about your use case to make more precise recommendations.

from swift-openapi-generator.

marius-se avatar marius-se commented on July 17, 2024

I essentially have two types of endpoints:

  1. Public endpoints: Don't need any middleware
  2. Private endpoints: Only authenticated requests should reach the request handler.

In Vapor we usually solve this using:

routes.get("overview", use: handler1)
routes.get("another-public-route": use: handler2)

// all routes registered under "private" will use AuthenticatorMiddleware
let private = routes.grouped(AuthenticatorMiddleware())
private.get("delete-me", use: handler3)
private.post("create-me", use: handler4)

And realistically there will be more endpoint-specific middlewares (e.g. some routes may require admin privileges etc.)

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on July 17, 2024

Gotcha. So if you don't want to have to maintain a manual list in your auth middleware that lets some operations through without auth and requires it for others, he's another idea.

Step 1: Use tags in your OpenAPI document to tag the operations that require authentication (e.g. called authed).
Step 2: Tag all the unauthed ones with a different one (e.g. called unauthed).
Step 3: Have 2 targets, e.g. called AuthedRoutes and UnauthedRoutes, and set up the generator for each of them, and in their config files, use the filter functionality to only include the authed and unauthed ones, respectively. Each would have its own APIProtocol protocol and its own handler implementation, e.g. called AuthedHandler and UnauthedHandler.
Step 4: In your server's executable, register them to the same server, such as:

import AuthedRoutes
import UnauthedRoutes

let app = Vapor.Application()

let transport = VaporTransport(routesBuilder: app)

// Register authed handler
let authedHandler = AuthedHandler()
try authedHandler.registerHandlers(on: transport, middlewares: [
  AuthenticationMiddleware()
])

// Register unauthed handler
let unauthedHandler = UnauthedHandler()
try unauthedHandler.registerHandlers(on: transport)

try app.run()

Could this work for you?

// Edit: You wouldn't need two copies of the OpenAPI document, you can use symlinks for the openapi.yaml required file in each target, but you can have only one copy of the document, e.g. at the root of your package.

from swift-openapi-generator.

czechboy0 avatar czechboy0 commented on July 17, 2024

Yeah it's not ideal, it's just a workaround for the lack of full support for security schemes in Swift OpenAPI Generator.

Once that's supported, you'd get all of the expected behavior almost for free.

But adding this feature to Swift OpenAPI Generator will likely need to be contributed by someone who needs it, with our guidance. Could be a fun project 🙂

from swift-openapi-generator.

jordanebelanger avatar jordanebelanger commented on July 17, 2024

On my side I am using the URLSessionTransport for an iOS application, how can I pass in the bearer token to my API calls? The API I am using does not explicitly specify the Authorization header in its routes, but it does have the security field in the route for a bearer token security scheme.

I tried subclassing URLSessionTransport to hijack the send method to inject my token only to find out that it's a struct so it cannot be subclassed.

Is there any easier way to do this? Surely I am missing something here 🤷

from swift-openapi-generator.

tib avatar tib commented on July 17, 2024

Quick question: is this a valid example?

enum TaskLocals {

    @TaskLocal
    static var authorization: String?
}

public struct AuthMiddleware: ServerMiddleware {

    public func intercept(
        _ request: HTTPRequest,
        body: HTTPBody?,
        metadata: ServerRequestMetadata,
        operationID: String,
        next: @Sendable (HTTPRequest, HTTPBody?, ServerRequestMetadata)
            async throws -> (HTTPResponse, HTTPBody?)
    ) async throws -> (HTTPResponse, HTTPBody?) {
        let auth = request.headerFields[.authorization]
        return try await TaskLocals.$authorization.withValue(auth) {
            try await next(request, body, metadata)
        }
    }
}

extension APIGateway {

    package func myHandler(
        _ input: Operations.myHandler.Input
    ) async throws -> Operations.myHandler.Output {
        print("--------------------------------------------------")
        print(TaskLocals.authorization)
        print("--------------------------------------------------")
        fatalError()
    }
}

I don't need to reach the HB / Vapor layer, but only the OAPI runtime.

What do you think? 🤔

Thanks,
Tib

from swift-openapi-generator.

daliad007 avatar daliad007 commented on July 17, 2024

Hello,

We're using ClientMiddleware to insert to correct auth info for the requests. However, when the user logs out / a different user logs in, those details change. To be able to set the new details on the client, the client object needs to be recreated as it doesn't seem to be a way to change the middleware once the client is instantiated. Is there a better way than this?

Thanks,
Dalia

from swift-openapi-generator.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.