Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Add third-party oauth methods #14

Open
dev4jam opened this issue May 25, 2018 · 12 comments
Open

Suggestion: Add third-party oauth methods #14

dev4jam opened this issue May 25, 2018 · 12 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@dev4jam
Copy link

dev4jam commented May 25, 2018

Add third-party OAuth methods: FB, Twitter, Google, etc

@proggeramlug
Copy link
Member

I love the idea, we need to do some research to figure out how to best solve this in a way that is not adding much overhead while also solving this generically.

The user manager is used for websites as well as apps as well as other services and so the solution needs to work for all of them.

Initially I would say that it should be like an "extra package" that can be installed as well but does not have to be. Thoughts on that? (this is open discussion)

@proggeramlug proggeramlug added enhancement New feature or request question Further information is requested labels May 25, 2018
@calebkleveter
Copy link
Contributor

In my spare time I support a Vapor Community package called Imperial. This package handles that kind of auth, but aims for towards user end auth, meaning it redirects the user from your app to the OAuth provider's authentication page and then back again.

UserManager is an API for an app. There is no user interface (though one could be added). For the most part, this functionality should be handled by the app that connects to the UserManager's API.

@dev4jam
Copy link
Author

dev4jam commented May 29, 2018

@proggeramlug it's hard to say... basically it's an extension of AuthController (or just another FacebookAuthController, GoogleAuthController, etc). But this should be still a part of Auth service.

@dev4jam
Copy link
Author

dev4jam commented May 29, 2018

@calebkleveter, as I see Imperial does all the magic regarding the social oauth flow. it's not the part of API based service though because client sends ready-to-use social auth token to the server to process registration or sign-in.

@dev4jam
Copy link
Author

dev4jam commented May 29, 2018

Trying to implement FB sign-in/sign-up... What do you think:

struct FacebookData: Codable {
    var name : String
    var email : String
}

final class FacebookAuthController: RouteCollection {
    func boot(router: Router) throws {
        router.post("fbSignin", use: signin)
    }

    func signin(_ request: Request) throws -> Future<LoginResponse> {
        let signer = try request.make(JWTService.self)
        let client = try request.make(Client.self)
        let fbToken = try request.content.syncGet(String.self, at: "token")
        let facebookURL = "https://graph.facebook.com/me?fields=email,name,id,gender&access_token=\(fbToken)"

        let facebookResponse = client.get(facebookURL)
            .flatMap(to: FacebookData.self) { try $0.content.decode(FacebookData.self) }

        return facebookResponse.flatMap(to: (User?, FacebookData).self) { fbUser in
            return try User.query(on: request).filter(\.email == fbUser.email).first().and(result: fbUser)
        }
        .flatMap(to: User.self) { userInfo in
            if let user = userInfo.0 {
                return request.eventLoop.newSucceededFuture(result: user)
            } else {
                let user = try User(userInfo.1.email, "en")

                user.firstname = userInfo.1.name
                user.confirmed = true
                user.password = try BCrypt.hash(fbToken)

                return request.eventLoop.newSucceededFuture(result: user)
            }
        }
        .flatMap(to: User.self) { user in
            return user.save(on: request)
        }
        .flatMap(to: LoginResponse.self) { user in
            let userPayload = try Payload(user: user)

            let remotePayload = try request.payloadData(
                signer.sign(userPayload),
                with: ["userId": "\(user.requireID())"],
                as: JSON.self
            )

            // Create a response form the access token, refresh token. and user response data.
            return remotePayload.map(to: LoginResponse.self) { remotePayload in
                let payload = try remotePayload.merge(userPayload.json())

                let accessToken = try signer.sign(payload)
                let refreshToken = try signer.sign(RefreshToken(user: user))

                guard user.confirmed else { throw Abort(.badRequest, reason: "User not activated.") }

                let userResponse = UserResponse(user: user, attributes: nil)
                
                return LoginResponse(accessToken: accessToken, refreshToken: refreshToken, user: userResponse)
            }
        }
    }
}

@proggeramlug
Copy link
Member

I'll need to play with it some more but I think this looks pretty good already!

I think we should offer this as some SPM package that can be included into the user-manager but doesn't necessarily have to. So people could configure their own flavor by just removing/adding the social media extensions they need.

What do you think? @calebkleveter @dev4jam

@calebkleveter
Copy link
Contributor

I like it. Just note I don't have platforms such as Facebook, so I wouldn't be able to implement those providers.

@proggeramlug
Copy link
Member

@dev4jam If you feel comfortable with that feel free to create such a package for Facebook since you have the code already - contact me or Caleb for any support you may need.

I'm thinking we should have packages such as:
UserManager-Facebook
UserManager-Twitter
UserManager-LinkedIn
UserManager-Google
etc.

@calebkleveter
Copy link
Contributor

@proggeramlug Would this be server-to-server authentication?

@proggeramlug
Copy link
Member

In parts, depending on the platform it all works generically speaking like this:

  1. Request some token from (facebook/twitter/etc)
  2. Lead the user to a confirmation page that will authenticate this request
  3. receive your token and use it to verify the user

In some cases they actually do use JWTs for this or some variation of JWT. So there is some server-to-server but in all cases the user has to "manually" confirm it as well.

@dev4jam
Copy link
Author

dev4jam commented May 30, 2018

This is actually my first server-side code on Vapor... So I might not know some specifics... For example, I don't really understand how to register (in the router) this controller if it will be located in a separate package. But I can try :)

@proggeramlug
Copy link
Member

My respect for trying and being involved, that is awesome!

If you just go ahead and put the controllers in a rep, I'm fairly sure we can figure out how to register them well. It probably needs to go through some middleware/service in the config.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants