Skip to content

Commit

Permalink
Soto v5 (#18)
Browse files Browse the repository at this point in the history
* Use aws-sdk-swift-master branch of aws-cognito-authentication-kit

* Add AWS prefix in exports.swift

* Use soto-v5 branch

* Remove unnecessary import

* Update exports

* Rename to SotoCognitoAuthentication

* Replace AWS prefix with Soto

* Remove Soto Prefix

* Update README for change API

* Use 2.0.0 of soto-cognito-authentication-kit

* Rename to +Cognito
  • Loading branch information
adam-fowler authored Dec 7, 2020
1 parent 2ce1a4c commit 024bb27
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 54 deletions.
10 changes: 5 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
import PackageDescription

let package = Package(
name: "aws-cognito-authentication",
name: "soto-cognito-authentication",
platforms: [.macOS(.v10_15)],
products: [
.library(name: "AWSCognitoAuthentication", targets: ["AWSCognitoAuthentication"]),
.library(name: "SotoCognitoAuthentication", targets: ["SotoCognitoAuthentication"]),
],
dependencies: [
.package(url: "https://github.com/adam-fowler/aws-cognito-authentication-kit.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/adam-fowler/soto-cognito-authentication-kit.git", from: "2.0.0"),
.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0")),
],
targets: [
.target(name: "AWSCognitoAuthentication", dependencies: [
.product(name: "AWSCognitoAuthenticationKit", package: "aws-cognito-authentication-kit"),
.target(name: "SotoCognitoAuthentication", dependencies: [
.product(name: "SotoCognitoAuthenticationKit", package: "soto-cognito-authentication-kit"),
.product(name: "Vapor", package: "vapor")
])
]
Expand Down
33 changes: 16 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
# AWS Cognito Authentication
# Soto Cognito Authentication
[<img src="http://img.shields.io/badge/swift-5.1-brightgreen.svg" alt="Swift 5.1" />](https://swift.org)
[<img src="https://github.com/adam-fowler/aws-cognito-authentication/workflows/Swift/badge.svg" />](https://github.com/adam-fowler/aws-cognito-authentication/actions?query=workflow%3ASwift)
[<img src="https://github.com/adam-fowler/soto-cognito-authentication/workflows/Swift/badge.svg" />](https://github.com/adam-fowler/soto-cognito-authentication/actions?query=workflow%3ASwift)

This is the Vapor wrapper for [AWS Cognito Authentication Kit](https://github.com/adam-fowler/aws-cognito-authentication-kit). It provides application storage for configurations and authentication calls on request. Documentation on AWS Cognito Authentication Kit can be found [here](https://github.com/adam-fowler/aws-cognito-authentication-kit/blob/main/README.md)
This is the Vapor wrapper for [Soto Cognito Authentication Kit](https://github.com/adam-fowler/soto-cognito-authentication-kit). It provides application storage for configurations and authentication calls on request. Documentation on Soto Cognito Authentication Kit can be found [here](https://github.com/adam-fowler/soto-cognito-authentication-kit/blob/main/README.md)

# Using with Vapor
## Configuration
Store your AWSCognitoConfiguration on the Application object. In configure.swift add the following with your configuration details
Store your `CognitoConfiguration` on the Application object. In configure.swift add the following with your configuration details
```swift
let awsCognitoConfiguration = AWSCognitoConfiguration(
let awsCognitoConfiguration = CognitoConfiguration(
userPoolId: String = "eu-west-1_userpoolid",
clientId: String = "23432clientId234234",
clientSecret: String = "1q9ln4m892j2cnsdapa0dalh9a3aakmpeugiaag8k3cacijlbkrp",
cognitoIDP: CognitoIdentityProvider = CognitoIdentityProvider(region: .euwest1),
region: Region = .euwest1
cognitoIDP: CognitoIdentityProvider = CognitoIdentityProvider(region: .euwest1)
)
app.awsCognito.authenticatable = AWSCognitoAuthenticatable(configuration: awsCognitoConfiguration)
app.cognito.authenticatable = CognitoAuthenticatable(configuration: awsCognitoConfiguration)
```
The CognitoIdentity configuration can be setup in a similar way.
```swift
let awsCognitoIdentityConfiguration = AWSCognitoIdentityConfiguration(
let awsCognitoIdentityConfiguration = CognitoIdentityConfiguration(
identityPoolId: String = "eu-west-1_identitypoolid"
identityProvider: String = "provider"
cognitoIdentity: CognitoIdentity = CognitoIdentity(region: .euwest1)
)
let app.awsCognito.identifiable = AWSCognitoIdentifiable(configuration: awsCognitoIdentityConfiguration)
let app.cognito.identifiable = CognitoIdentifiable(configuration: awsCognitoIdentityConfiguration)
```
## Accessing functionality
Functions like `createUser`, `signUp`, `authenticate` with username and password and `responseToChallenge` are all accessed through `request.application.awsCognito.authenticatable`. Extend `AWSCognitoAuthenticateResponse` to conform to `Content` and the following login route will return the full response from `AWSCognitoAuthenticable.authenticate`.
Functions like `createUser`, `signUp`, `authenticate` with username and password and `responseToChallenge` are all accessed through `request.application.cognito.authenticatable`. The following login route will return the full response from `CognitoAuthenticable.authenticate`.
```swift
func login(_ req: Request) throws -> EventLoopFuture<AWSCognitoAuthenticateResponse> {
func login(_ req: Request) throws -> EventLoopFuture<CognitoAuthenticateResponse> {
let user = try req.content.decode(User.self)
return req.application.awsCognito.authenticatable.authenticate(
return req.application.cognito.authenticatable.authenticate(
username: user.username,
password: user.password,
context: req,
Expand All @@ -41,15 +40,15 @@ Functions like `createUser`, `signUp`, `authenticate` with username and password
If id, access or refresh tokens are provided in the 'Authorization' header as Bearer tokens the following functions in Request can be used to verify them `authenticate(idToken:)`, `authenticate(accessToken:)`, `refresh`. as in the following
```swift
func authenticateAccess(_ req: Request) throws -> Future<> {
req.awsCognito.authenticateAccess().flatMap { _ in
req.cognito.authenticateAccess().flatMap { _ in
...
}
}
```

## Authenticators

Three authenticators are available. See the [Vapor docs](https://docs.vapor.codes/4.0/authentication) for more details on authentication in Vapor.`AWSCognitoBasicAuthenticator` will do username, password authentication and returns a `AWSCognitoAuthenticateResponse`. `AWSCognitoAccessAuthenticator` will do access token authentication and returns an `AWSCognitoAccessToken` which holds all the information that could be extracted from the access token. `AWSCognitoIdAuthenticator<Payload>` does id token authentication and extracts information from the id token into your own `Payload` type. The standard list of claims that can be found in an id token are detailed in the [OpenID spec] (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). Your `Payload` type needs to decode using these tags, the username tag "cognito:username" and any custom tags you may have setup for the user pool. Below is an example of using the id token authenticator.
Three authenticators are available. See the [Vapor docs](https://docs.vapor.codes/4.0/authentication) for more details on authentication in Vapor.`CognitoBasicAuthenticator` will do username, password authentication and returns a `CognitoAuthenticateResponse`. `CognitoAccessAuthenticator` will do access token authentication and returns an `CognitoAccessToken` which holds all the information that could be extracted from the access token. `CognitoIdAuthenticator<Payload>` does id token authentication and extracts information from the id token into your own `Payload` type. The standard list of claims that can be found in an id token are detailed in the [OpenID spec] (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). Your `Payload` type needs to decode using these tags, the username tag "cognito:username" and any custom tags you may have setup for the user pool. Below is an example of using the id token authenticator.

First create a User type to store your id token payload in.
```swift
Expand All @@ -63,9 +62,9 @@ struct User: Content & Authenticatable {
}
}
```
Add a route using the authenticator. The `AWSCognitoIdAuthenticator` authenticates the request, the `guardMiddleware` ensures the user if authenticated. The actual function accesses the `User` type via `req.auth.require`.
Add a route using the authenticator. The `CognitoIdAuthenticator` authenticates the request, the `guardMiddleware` ensures the user if authenticated. The actual function accesses the `User` type via `req.auth.require`.
```swift
app.grouped(AWSCognitoIdAuthenticator<User>())
app.grouped(CognitoIdAuthenticator<User>())
.grouped(User.guardMiddleware())
.get("user") { (req) throws -> EventLoopFuture<User> in
let user = try req.auth.require(User.self)
Expand Down
3 changes: 0 additions & 3 deletions Sources/AWSCognitoAuthentication/Exports.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import Vapor


public extension Application {
var awsCognito: AWSCognito {
var cognito: SotoCognito {
.init(application: self)
}

struct AWSCognito {
struct SotoCognito {
struct AuthenticatableKey: StorageKey {
typealias Value = AWSCognitoAuthenticatable
typealias Value = CognitoAuthenticatable
}

public var authenticatable: AWSCognitoAuthenticatable {
public var authenticatable: CognitoAuthenticatable {
get {
guard let authenticatable = self.application.storage[AuthenticatableKey.self] else {
fatalError("AWSCognito authenticatable not setup. Use application.awsCognito.authenticatable = ...")
Expand All @@ -24,10 +24,10 @@ public extension Application {
}

struct IdentifiableKey: StorageKey {
typealias Value = AWSCognitoIdentifiable
typealias Value = CognitoIdentifiable
}

public var identifiable: AWSCognitoIdentifiable {
public var identifiable: CognitoIdentifiable {
get {
guard let identifiable = self.application.storage[IdentifiableKey.self] else {
fatalError("AWSCognito identifiable not setup. Use application.awsCognito.identifiable = ...")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import AWSCognitoAuthenticationKit
import AWSSDKSwiftCore
import SotoCognitoAuthenticationKit
import NIO
import Vapor

extension AWSCognitoAuthenticateResponse: Authenticatable {}
extension AWSCognitoAccessToken: Authenticatable {}
extension CognitoAuthenticateResponse: Authenticatable {}
extension CognitoAccessToken: Authenticatable {}

public typealias AWSCognitoBasicAuthenticatable = AWSCognitoAuthenticateResponse
public typealias AWSCognitoAccessAuthenticatable = AWSCognitoAccessToken
public typealias CognitoBasicAuthenticatable = CognitoAuthenticateResponse
public typealias CognitoAccessAuthenticatable = CognitoAccessToken

/// Authenticator for Cognito username and password
public struct AWSCognitoBasicAuthenticator: BasicAuthenticator {
public struct CognitoBasicAuthenticator: BasicAuthenticator {

public init() {}

public func authenticate(basic: BasicAuthorization, for request: Request) -> EventLoopFuture<Void> {
return request.application.awsCognito.authenticatable.authenticate(username: basic.username, password: basic.password, context: request, on:request.eventLoop).map { token in
return request.application.cognito.authenticatable.authenticate(username: basic.username, password: basic.password, context: request, on:request.eventLoop).map { token in
request.auth.login(token)
}.flatMapErrorThrowing { error in
switch error {
Expand All @@ -30,12 +29,12 @@ public struct AWSCognitoBasicAuthenticator: BasicAuthenticator {
}

/// Authenticator for Cognito access tokens
public struct AWSCognitoAccessAuthenticator: BearerAuthenticator {
public struct CognitoAccessAuthenticator: BearerAuthenticator {

public init() {}

public func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture<Void> {
return request.application.awsCognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop).map { token in
return request.application.cognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop).map { token in
request.auth.login(token)
}.flatMapErrorThrowing { error in
switch error {
Expand All @@ -52,12 +51,12 @@ public struct AWSCognitoAccessAuthenticator: BearerAuthenticator {
/// Authenticator for Cognito id tokens. Can use this to extract information from Id Token into Payload struct. The list of standard list of claims found in an id token are
/// detailed in the [OpenID spec] (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) . Your `Payload` type needs
/// to decode using these tags, plus the AWS specific "cognito:username" tag and any custom tags you have setup for the user pool.
public struct AWSCognitoIdAuthenticator<Payload: Authenticatable & Codable>: BearerAuthenticator {
public struct CognitoIdAuthenticator<Payload: Authenticatable & Codable>: BearerAuthenticator {

public init() {}

public func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture<Void> {
return request.application.awsCognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop).map { (payload: Payload)->() in
return request.application.cognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop).map { (payload: Payload)->() in
request.auth.login(payload)
}.flatMapErrorThrowing { error in
switch error {
Expand Down
3 changes: 3 additions & 0 deletions Sources/SotoCognitoAuthentication/Exports.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@_exported import SotoCognitoAuthenticationKit
@_exported import SotoCognitoIdentity
@_exported import SotoCognitoIdentityProvider
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import AWSCognitoAuthenticationKit
import SotoCognitoAuthenticationKit
import Vapor

// extend AWSCognitoAuthenticateResponse so it can be returned from a Vapor route
extension AWSCognitoAuthenticateResponse: Content {}
extension CognitoAuthenticateResponse: Content {}

public extension Request {

var awsCognito: AWSCognito {
var cognito: SotoCognito {
.init(request: self)
}

struct AWSCognito {
struct SotoCognito {

/// helper function that returns if request with bearer token is cognito access authenticated
/// - returns:
/// An access token object that contains the user name and id
public func authenticateAccess() -> EventLoopFuture<AWSCognitoAccessToken> {
public func authenticateAccess() -> EventLoopFuture<CognitoAccessToken> {
guard let bearer = request.headers.bearerAuthorization else {
return request.eventLoop.makeFailedFuture(Abort(.unauthorized))
}
return request.application.awsCognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop)
return request.application.cognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop)
}

/// helper function that returns if request with bearer token is cognito id authenticated and returns contents in the payload type
Expand All @@ -29,17 +29,17 @@ public extension Request {
guard let bearer = request.headers.bearerAuthorization else {
return request.eventLoop.makeFailedFuture(Abort(.unauthorized))
}
return request.application.awsCognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop)
return request.application.cognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop)
}

/// helper function that returns refreshed access and id tokens given a request containing the refresh token as a bearer token
/// - returns:
/// The payload contained in the token. See `authenticate<Payload: Codable>(idToken:on:)` for more details
public func refresh(username: String) -> EventLoopFuture<AWSCognitoAuthenticateResponse> {
public func refresh(username: String) -> EventLoopFuture<CognitoAuthenticateResponse> {
guard let bearer = request.headers.bearerAuthorization else {
return request.eventLoop.makeFailedFuture(Abort(.unauthorized))
}
return request.application.awsCognito.authenticatable.refresh(username: username, refreshToken: bearer.token, context: request, on: request.eventLoop)
return request.application.cognito.authenticatable.refresh(username: username, refreshToken: bearer.token, context: request, on: request.eventLoop)
}

/// helper function that returns AWS credentials for a provided identity. The idToken is provided as a bearer token.
Expand All @@ -50,7 +50,7 @@ public extension Request {
guard let bearer = request.headers.bearerAuthorization else {
return request.eventLoop.makeFailedFuture(Abort(.unauthorized))
}
let identifiable = request.application.awsCognito.identifiable
let identifiable = request.application.cognito.identifiable
return identifiable.getIdentityId(idToken: bearer.token, on: request.eventLoop)
.flatMap { identity in
return identifiable.getCredentialForIdentity(identityId: identity, idToken: bearer.token, on: self.request.eventLoop)
Expand All @@ -62,7 +62,7 @@ public extension Request {
}

/// extend Vapor Request to provide Cognito context
extension Request: AWSCognitoContextData {
extension Request: CognitoContextData {
public var contextData: CognitoIdentityProvider.ContextDataType? {
let host = headers["Host"].first ?? "localhost:8080"
guard let remoteAddress = remoteAddress else { return nil }
Expand Down

0 comments on commit 024bb27

Please sign in to comment.