Giter Club home page Giter Club logo

turnstile's Introduction

#Stormpath is Joining Okta We are incredibly excited to announce that Stormpath is joining forces with Okta. Please visit the Migration FAQs for a detailed look at what this means for Stormpath users.

We're available to answer all questions at [email protected].

Turnstile

Build Status codecov codebeat badge Slack Status

Turnstile is a security framework for Swift inspired by Apache Shiro. It's used to manage the currently executing user account in your application, whether iOS app or backend web application.

Overview

Turnstile is the easiest way to add authentication to your Swift apps. Currently, the focus is on a great API for backend Swift apps.

Turnstile is split into three projects:

  • Turnstile Core - provides key components for authentication and security.
  • Turnstile Crypto - tools for generating randomness, hashing, and encrypting data
  • Turnstile Web - integrations with Facebook, Google, and other helpers useful for backend web applications.

If you're a developer of an application or product and need to integrate Turnstile, read the docs for Turnstile Core. Otherwise, if you're a developer using a Turnstile integration, read the docs for Turnstile Web.

Getting Started

The easiest way to use Turnstile is with one of its prebuilt integrations with Swift web frameworks. Here are the frameworks and their statuses:

Using Turnstile

If you'd like to use Turnstile to build your own integration, it's useful to understand key concepts in Turnstile.

Subject

The Subject represents the currently operating user for your application. You'll use this to interact with Turnstile, and safely check if the current user is authenticated properly.

The Subject API in Turnstile also supports registration, however this is a convenience for basic use cases. Since different apps have different rules on registration and user managment, it's expected that you will most likely write your own registration and user management logic.

Realm

A realm connects Turnstile to your data store, and allows Turnstile to authenticate and register accounts. Included with Turnstile is a MemoryRealm, as well as a WebMemoryRealm which can handle username/password pairs, as well as Facebook and Google login.

The MemoryRealms store information in memory, and will be wiped when the application is restarted.

To write your own Realm, you'll need to implement the Realm protocol, which is defined as:

public protocol Realm {
  func authenticate(credentials: Credentials) throws -> Account
  func register(credentials: Credentials) throws -> Account
}

Turnstile provides Credentials objects for common use cases, like UsernamePassword, APIKey, and AccessToken. Feel free to define a custom type as well.

When Turnstile calls the authenticate or register functions, your Realm should check the Credential type and make sure that it's a credential type you support. If not, it should throw UnsupportedCredentialsError().

Afterwards, your Realm should check if the credentials are valid. If not, it should throw IncorrectCredentialsError().

If the credentials are correct, you should then authenticate the user, and return the account object. The account protocol is simple:

public protocol Account {
    var uniqueID: String { get }
}

And voila! You've created your first Realm!

SessionManager

SessionManager is a Turnstile component that manages sessions and persistience for your authentication system. Included with Turnstile is a MemorySessionmanager, which can persist sessions in memory.

If you're building your own, you'll need to implement the SessionManager protocol. This is defined as:

public protocol SessionManager {
    /// Creates a session for a given Account object and returns the identifier.
    func createSession(account: Account) -> String
    
    /// Gets the account ID for the current session identifier.
    func restoreAccount(fromSessionID identifier: String) throws -> Account

    /// Destroys the session for a session identifier.
    func destroySession(identifier: String)
}

When an account is authenticated, and is asks to use the session manager to persist its data, Turnstile calls createSession(account:) and expects the Session Manager to return a SessionID it can use to restore the account. While you can use whatever you want as a Session ID, we recommend using TurnstileCrypto's Random.secureToken method to generate a random string with 128 bits of entropy.

When a user comes in with the SessionID, Turnstile calls restoreAccount(fromSessionID:) and expects the session manager to return the associated account. Note that this can be different from the account in the Realm, since you might not want to make a database call on every request. If the session does not exist, the Session Manager should throw InvalidSessionError()

When the user logs out, destroySession(identifier:) should delete the session from the session store.

Turnstile Web

Turnstile Web provides a number of helpers to make authentication for websites easier. TurnstileWeb includes plugins for external login providers, like Facebook, Google, and Digits.

OAuth2: Authenticating with Facebook or Google

The Facebook and Google Login flows look like the following:

  1. Your web application redirects the user to the Facebook / Google login page, and saves a "state" to prevent a malicious attacker from hijacking the login session.
  2. The user logs in.
  3. Facebook / Google redirects the user back to your application.
  4. The application validates the Facebook / Google token as well as the state, and logs the user in.

Create a Facebook Application

To get started, you first need to register an application with Facebook. After registering your app, go into your app dashboard's settings page. Add the Facebook Login product, and save the changes.

In the Valid OAuth redirect URIs box, type in a URL you'll use for step 3 in the OAuth process. (eg, http://localhost:8080/login/facebook/consumer)

Create a Google Application

To get started, you first need to register an application with Google. Click "Enable and Manage APIs", and then the credentials tab. Create an OAuth Client ID for "Web".

Add a URL you'll use for step 3 in the OAuth process to the Authorized redirect URIs list. (eg, http://localhost:8080/login/google/consumer)

Initiating the Login Redirect

TurnstileWeb has Facebook and Google objects, which will allow a you to set up your configured application and log users in. To initialize them, use the client ID and secret (sometimes called App ID) from your Facebook or Google developer console:

let facebook = Facebook(clientID: "clientID", clientSecret: "clientSecret")
let google = Google(clientID: "clientID", clientSecret: "clientSecret")

Then, generate a state (you can use Random.secureToken to generate a random string), save it (we recommend setting a cookie on your user's browser), and redirect the user:

// Redirect the user to this URL using your web framework:
facebook.getLoginLink(redirectURL: "http://localhost:8080/login/google/consumer", state: state)

Consuming the Login Response

Once the user is redirected back to your application, you can now verify that they've properly authenticated using the state from the earlier step, and the full URL that the user has been redirected to:

let credentials = try facebook.authenticate(authorizationCodeCallbackURL: url, state: state) as! FacebookAccount
let credentials = try google.authenticate(authorizationCodeCallbackURL: url, state: state) as! GoogleAccount

These can throw the following errors:

  • InvalidAuthorizationCodeError if the Authorization Code could not be validated
  • APIConnectionError if we cannot connect to the OAuth server
  • InvalidAPIResponse if the server does not respond in a way we expect
  • OAuth2Error if the OAuth server calls back with an error

If successful, it will return a FacebookAccount or GoogleAccount. These implement the Credentials protocol, so then can be passed back into your application's Realm for further validation.

OAuthEcho: Authenticating with Twitter or Digits

The Digits Login flows look like the following:

  1. Your application prompts for login either with Twitter or Digits
  2. The user selects one and logs in.
  3. Digits generates two special headers:
  • X-Auth-Service-Provider the endpoint where the user needs to be authenticated through
  • X-Verify-Credentials-Authorization the OAuth token
  1. The application validates the information in the generated headers
  2. The application makes a GET request to the URL in the X-Auth-Service-Provider header with the X-Verify-Credentials-Authorization header added as the Authorization header in this request.
  3. The response is validated to authorize the user and logs them in

Create a Digits Application

The easiest way to setup your app with Digits is to use the Fabric app. After you go through the setup you will then be able to access the consumerKey and consumerSecret in the Fabric web interface.

Example implementation

let digits = Digits(consumerKey: "consumerKeyGoesHere")
guard
    let urlString = request.headers["X-Auth-Service-Provider"],
    let url = URL(string: urlString),
    let authHeader = request.headers["X-Verify-Credentials-Authorization"],
    let oauthParams = OAuthParameters(header: authHeader)
else {
    throw Abort.custom(status: .unauthorized, message: "Bad Digits headers")
}

let credentials: Credentials? = OAuthEcho(authServiceProvider: url, oauthParameters: oauthParams)
let account = try digits.authenticate(credentials: credentials!) as! DigitsAccount

For an example of Digits in action using Vapor checkout this app

TurnstileCrypto

Turnstile Crypto has tools to help you build authentication in your apps. Specifically, it can help you use BCrypt hashing in your app, as well as generate secure random numbers. Documentation is in the files themselves.

Tests

Tests are powered by XCTest. To successfully perform the Facebook Login tests, you must have the following environment variables set:

Contributing

We're always open to contributions! Feel free to join the Stormpath slack channel to discuss how you can contribute!

Stormpath

Turnstile is built by Stormpath, an API service for authentication, authorization, and user management. If you're building a website, API, or app, and need to build authentication and user management, consider using Stormpath for your needs. We're always happy to help!

turnstile's People

Contributors

brentstormpath avatar edjiang avatar gomfucius avatar harlanhaskins avatar kdawgwilk avatar loganwright avatar robwettach avatar siemensikkema avatar tanner0101 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  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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

turnstile's Issues

New crendentials

If I want to add a new credentails Protocol to Turnstile.
Where can I put this?

CredentialsError always return 500 server error?

I'm working on implementing user login with Vapor, I'm using the built in Turnstyle CredentialsErrors, however they don't seem to implement the correct HTTP error codes. For example, when I setup my ProtectMiddleware.

ProtectMiddleware(error: IncorrectCredentialsError())

The response I get on the client is a 500 with the message IncorrectCredentialsError: Invalid Credentials:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json; charset=utf-8
Content-Length: 100

{"code":500,"error":true,"message":"IncorrectCredentialsError: Invalid Credentials","metadata":null}

I would expect 403 Forbidden.

BCrypt code quality

I'm not happy with the code quality of the BCrypt implementation I imported. This is one of the few BCrypt implementations in Swift (thanks @Joannis for porting @meanjoe45's Swift 1 implementation), so will have to do for now.

BCrypt is passing tests for now, so I'll clean up the API and punt on the decision to audit / improve the code quality myself, or wait for a more popular BCrypt library to pop up on Swift.

Support for queries in the url

In
func exchange(authorizationCodeCallbackURL url: String, state: String) throws -> OAuth2Token {

I think you should use:
let redirectURL = url.substring(to: url.range(of: "code")?.lowerBound ?? url.startIndex)

because if you take for the redirection url everything under ? then you end up with a url that doesn't include additional parameters that could have been added in that url.

e.g.."http://0.0.0.0:8080/login-with-facebook/consumer?normal=true
normal = true will get cut off

Credentials

How can i implement my own class inheriting Credentials?
Always gets UnsupportedCredentialsError with login/register methods

I tried to inherit from UsernamePassword - that worked, but i had to make some changes in this class.

is there any good way implementing own Credentials?

fatal error: Transfer completed, but there's no current request. NSURLSession

When running in Linux with Swift version 3.0.2 after receiving a few requests to my Vapor server (perhaps in quick succession) I get the following fatal error:

fatal error: Transfer completed, but there's no current request.: file Foundation/NSURLSession/NSURLSessionTask.swift, line 794

Thanks to @loganwright I narrowed the problem down to authenticating the incoming request with Turnstile Web. The issue only happens on Linux not macOS but is quite sporadic. The error occurs when Turnstile makes the request to Facebook.

Update to Swift 8-15

Blocked on vapor/engine, vapor/json, and SecretSocks. @tannernelson, I'm doing other work this week -- can you ping me when the vapor dependencies are ready? If SecretSocks needs work I can take a look into it.

AuthError.notAuthenticated after authenticating the user.

I'm using the Vapor framework for Swift.
And I can successful register and log users in. But when I add a custom Authentication middleware to a route where only Authenticated users should go, it gives me this response :

Uncaught Error: AuthError.notAuthenticated. Use middleware to catch this error and provide a better response. Otherwise, a 500 error page will be returned in the production environment.

By code is here : https://github.com/theobouwman/auth-vapor

Thanks in advance.

Allow `createSession` to throw

As I was writing my implementation of the protocol SessionManager, I realized I needed the createSession to be able to throw errors. After I create my session, I need to persist it to the our database, and that introduces a source of possible thrown errors that can not be propagated back to the network stack as createSession can not throw.

Would it be possible to change the method signature of the createSession method from

func createSession(account: Account) -> String

to

func createSession(account: Account) throws -> String

?

This would allow me to return an error in the HTTP request that initiated the session creation if the save operation failed.

typo in Credentials.swift

there is a typo at Sesssion with 3 's' :
public struct Sesssion: Credentials {}
should be :
public struct Session: Credentials {}

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.