Giter Club home page Giter Club logo

keychain's Introduction

Keychain ⛓

Swift Version Vapor Version tests Readme Score GitHub license

Keychain adds a complete and customizable user authentication system to your API project.

📦 Installation

Update your Package.swift file.

.package(url: "https://github.com/nodes-vapor/keychain.git", from: "2.0.0")
targets: [
    .target(
        name: "App",
        dependencies: [
            ...
            .product(name: "Keychain", package: "keychain"),
        ]
    ),
    ...
]

Usage

These are the steps required to use Keychain in your project.

  1. Define a Payload that conforms to the KeychainPayload protocol
  2. Create KeychainConfig objects for the key types you would like to use
  3. Configure your Keychain using a Signer and the KeychainConfig objects defined in step 2
  4. Actually start using your Keychain

Time to look at each step in detail.

Define a Payload

Your payload must conform to the KeychainPayload protocol, meaning that it must contain:

  • init(expirationDate: Date, user: User) throws
  • func findUser(request: Request) -> EventLoopFuture<User> which is where you do a search for the user you were presented in the init method
  • func verify(using signer: JWTSigner) throws which will verify that your token is still valid

Furthermore you need to tell your KeychainPayload what its associatedtype for User translates to.

Here is an example that uses elements from a JWT token and verifies that the expiration (exp) claim is not expired. Note that findUser in this case only returns a test user. In real life you probably want to do a lookup somewhere where users are stored.

import JWT
import Keychain
import Vapor

struct UserJWTPayload: KeychainPayload {
    let exp: ExpirationClaim
    let sub: SubjectClaim

    init(expirationDate: Date, user: User) {
        self.exp = .init(value: expirationDate)
        self.sub = .init(value: user.id)
    }

    func findUser(request: Request) -> EventLoopFuture<User> {
        request.eventLoop.future(request.testUser).unwrap(or: TestError.userNotFound)
    }

    func verify(using signer: JWTSigner) throws {
        try exp.verifyNotExpired()
    }
}

Create KeychainConfig Objects

Your KeychainConfig objects must contain:

  • an identifier (eg: access, refresh or reset): jwkIdentifier
  • an expirationTimeInterval

And you need to connect your KeychainConfig with the KeychainPayload you defined in step 1 (the KeychainConfig has a typealias for a KeychainPayload).

Here is an example creating three KeychainConfig objects:

  • A UserAccessKeychainConfig with the identifier "access" and an expirationTimeInterval of 300 seconds
  • A UserRefreshKeychainConfig with the identifier "refresh" and an expirationTimeInterval of 600 seconds
  • A UserResetKeychainConfig with the identifier "reset" and an expirationTimeInterval of 400 seconds
import JWT
import Keychain

struct UserAccessKeychainConfig: KeychainConfig, Equatable {
    typealias JWTPayload = UserJWTPayload

    static var jwkIdentifier: JWKIdentifier = "access"

    let expirationTimeInterval: TimeInterval = 300
}

struct UserRefreshKeychainConfig: KeychainConfig, Equatable {
    typealias JWTPayload = UserJWTPayload

    static var jwkIdentifier: JWKIdentifier = "refresh"

    let expirationTimeInterval: TimeInterval = 600
}

struct UserResetKeychainConfig: KeychainConfig, Equatable {
    typealias JWTPayload = UserJWTPayload

    static var jwkIdentifier: JWKIdentifier = "reset"

    let expirationTimeInterval: TimeInterval = 400
}

Configure your Keychain

Time to tie it all together! In your configure.swift you can add multiple KeychainConfig objects as seen here:

app.keychain.configure(
    signer: .hs256(key: YourKeyGoesHere...ProbablyReadFromSomeEnvironment),
    config: UserAccessKeychainConfig()
)
app.keychain.configure(
    signer: JWTSigner(
        algorithm: TestJWTAlgorithm(name: UserRefreshKeychainConfig.jwkIdentifier.string)
    ),
    config: UserRefreshKeychainConfig()
)
app.keychain.configure(
    signer: JWTSigner(
        algorithm: TestJWTAlgorithm(name: UserResetKeychainConfig.jwkIdentifier.string)
    ),
    config: UserResetKeychainConfig()
)

Note the signer parameter. You can use one of the built-in signers as in the first example where we use the .hs256 signer with a key. Alternatively, you can provide your own signer as it is done in the last two examples.

Actually start using your Keychain

With all the setup out of the way, it is time to kick back and take advantage of Keychain. You can now use the UserAccessKeychainConfig, UserRefreshKeychainConfig and UserResetKeychainConfig objects that you created previously to generate JWT tokens by calling the makeToken(on:, currentDate:)

Here is an example on how to generate a new refreshToken.

import Keychain

struct UserController {
    let currentDate: () -> Date

    ...

    func refreshToken(request: Request) throws -> Response {
        let token = try UserRefreshKeychainConfig.makeToken(on: request, currentDate: currentDate())

        // here we encode the token string as JSON but you might include your token in a struct
        // conforming to `Content`
        let response = Response()
        try response.content.encode(token, as: .json)
        return response
    }
}

🏆 Credits

This package is developed and maintained by the Vapor team at Monstarlab.

📄 License

This package is open-sourced software licensed under the MIT license

keychain's People

Contributors

brettrtoomey avatar casperhr avatar chriscombs avatar emarashliev avatar juliocbcotta avatar martinlasek avatar mauran avatar nickskull avatar pbodsk avatar rasmusebbesen avatar siemensikkema avatar steffendsommer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

keychain's Issues

Support OAuth flow with client token and refresh token

Hi, would be nice to have support to oauth with refresh token?
I am not quite sure about how to implement this auth format or if it is in the scope of this project, so I will let here what I think I know...

  • Client token: Static token, not refreshable, used to access public APIs, used in the Authorization header;
  • User token: Acquired on login, refreshable, used to access private APIs, used in the Authorization header;
  • Refresh token: refreshable when issuing new User token, used only in refresh token endpoint, never in the Authorization header;

Endpoint to login/refresh token
/api/v1/oauth

Credentials for login (may require Client token in Authorization header)
request body:

{
  "grant_type": "password",
  "email": "me@mail",
  "password": "secret"
}

Credentials for refresh_token (may require Client token in Authorization header)
request body:

{
  "grant_type": "refresh_token",
  "refresh_token": "blablabla"
}

response body:

{
  "token_type": "Bearer",
  "access_token": "asdasdasdasd",
  "refresh_token": "new blablabla",
  "expires_in": 3600
}

Would need more config entries in jwt.json to allow the configuration of TTL for the refresh_token and maybe a way to list the client tokens (since they are static, they can be seem as part of the config?)

If someone could give me an input about this before I start implementing something would be nice.

Consider auth errors

Consider better handling of wrong Authorization header. Currently returns 500 "Incorrect number of segments" if header has wrong format.

Make name on user optional

Or remove it completely. The bare minimum of information needed on the user should be email and password.

Log out not (always?) destroying sessions

I haven't looked into how/why this is happening, but I have experienced that a log out doesn't destroy the session. If you afterwards, call an endpoint using the same bearer token it will still work.

Project sample

Hi, would you mind to provide a project sample using this repo dependence ?

regenerate vs me

What is the big different right now?

Consider not having generate if me is doing the same

Reset pw is not working AT ALL

Going through the reset password flow the url for showing the form is returning a 404 (e.g. /reset-password/form/mytoken)

Vapor 3 Support?

Hello, given the upcoming RC of Vapor 3, are there plans to add Vapor 3 support to jwt-keychain?

Add signer warning

Consider adding warning in README to use different signers for different environments. Alternatively add environment identifier payload in JWT to make them incompatible between environments.

Remove password hash from JWTs

Currently the full password hash is included in all tokens as a way to make sure tokens are invalidated after a password reset. Although this does not directly enable unauthorized access it does expose the hashes unnecessarily. A more secure, but still simple, system would be to store a separate value on the user object that changes on each password change which will be compared to the value in incoming tokens. This value could be a counter, a date or a UUID.

Improve documentation

To make this package easier to pick up we should try to add more to the documentation such as examples and small tips and tricks (e.g. expire = 0)

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.