Vapor 4 Authentication Issue [User is not authenticated] - swift

I have an iOS client with OAuth 2.0 as an authenticating mechanism.
When a user signs in, I authenticate him with this method (Google sign in for example):
func processGoogleLogin(request: Request, token: String) throws -> EventLoopFuture<ResponseEncodable> {
try Google
.getUser(on: request)
.flatMap { userInfo in
User
.query(on: request.db)
.filter(\.$email == userInfo.email)
.first()
.flatMap { foundUser in
guard let existingUser = foundUser else {
//creating a new user
return user
.save(on: request.db)
.map {
request.session.authenticate(user)
//redirecting with info
}
}
request.session.authenticate(existingUser)
//redirecting with info
}
}
}
After the login, I want to check if the user is authenticated and if I've successfully authenticated the user.
So I have an endpoint that I protect from unauthenticated users, but even after signing in, the user cannot access this endpoint as he is not authenticated.
Error:
{
"error": true,
"reason": "User not authenticated."
}
My User Model conforms to ModelSessionAuthenticatable.
I also use the SessionMiddleware (ImpreialController is the auth controller):
let imperialController = ImperialController(sessionsMiddleware: app.sessions.middleware)
app.middleware.use(app.sessions.middleware)
In ImperialController:
class ImperialController {
private let sessionsMiddleware: Middleware
init(sessionsMiddleware: Middleware) {
self.sessionsMiddleware = sessionsMiddleware
}
....
And finally the protected route:
let protected = app.grouped(User.guardMiddleware())
protected.get { req -> HTTPResponseStatus in
return .ok
}

It may be that you are doing it, but just not showing it in your question. You need to create and register an instance of SessionsMiddleware using something like this:
app.middleware.use(SessionsMiddleware(session: MemorySessions(storage: MemorySessions.Storage())))
Do this before you create the instance of your controller.
EDIT in reply to OP' comment:
I normally pass the instances of the different middleware explicitly because I tend to apply subsets to groups of routes rather than all the middleware. For example:
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
app.middleware.use(SessionsMiddleware(session: MemorySessions(storage: MemorySessions.Storage())))
app.middleware.use(User.sessionAuthenticator(.mysql))
try app.register(collection: APIController(middleware: UserToken.authenticator()))
var middleware: [Middleware] = [CustomMiddleware.InternalErrorMiddleware()]
try app.register(collection: InsecureController(middleware: middleware))
middleware.append(contentsOf: [User.redirectMiddleware(path: [C.URI.Solidus].string),
User.authenticator(), User.guardMiddleware(),
CustomMiddleware.SessionTimeoutMiddleware()])
try app.register(collection: CustomerController(middleware: middleware))
BTW, have you included my line 3 above? That may be your problem.

Related

Associate user information from Cognito with AWS Amplify GraphQL

I am on xcode 11.4, Swift 4. The goal is to:
sign up a new user in Cognito User Pool, and then save an associated user record using Amplify GraphQL.
CRUD the user's record after signing in with Cognito User Pool.
The problem is I do not know how to associate Cognito with Amplify GraphQL. For example, in Google Firebase auth and Firestore, I would get a unique user id UID after signing up, then I would create an associated user record in Firestore with the key as this UID. Then on user signin/authentication, I can get this UID from firebase auth and find the associated record in firestore.
Currently with the AWS stack, I created a user model in schema.graphql as:
type User #model #auth(rules: [{ allow: owner, ownerField: "id", operations: [create, update, delete]}]){
id: ID!
firstName : String
lastName : String
handle : String
email : String!
}
So that only authenticated user can create, update and delete. Next somewhere in SignUpController I create a new user:
AWSMobileClient.default().signUp( username: email
, password: password
, userAttributes: ["email": email]) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
self.showAlert(msg: "You already have an account. Please go back and press log in")
case .unconfirmed:
break
case .unknown:
self.showAlert(msg: "Network error")
}
} else if let error = error { ... }
And then confirm the user w/ code:
AWSMobileClient.default().confirmSignUp(username: email, confirmationCode: code) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
// This is where I need to create an associated user account
break
case .unconfirmed:
self.showAlert(title: "Error", msg: "User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case .unknown:
self.showAlert(title: "Error", msg: "Network error")
}
} else { //if let error = error {
self.showAlert(title: "Error", msg: "Network error")
}
Right now my solution in case .confirmed is to sign in immediately, and then fetch the user's client token via:
class CognitoPoolProvider : AWSCognitoUserPoolsAuthProviderAsync {
/// this token may not be what you want ...
func getLatestAuthToken(_ callback: #escaping (String?, Error?) -> Void) {
AWSMobileClient.default().getTokens { (token, error) in
if let error = error {
callback(nil,error)
}
callback(token?.accessToken?.tokenString, error)
}
}
}
This turns out to be the wrong solution, since the user's client token changes all the time.
Overall, this is a standard hello-world problem, and there should be a standard out of box solution provided by AWS. I search the docs and github, but cannot find a satisfactory answer.
The right way is DON'T TRUST CLIENT for creating associate user information from Cognito, you have to do it at server side.
You should create a new Lambda Post Confirmation Trigger for Cognito and code it to create an associate account. You can use event.userName or create custom attribute uuid type likes custom:id to link your associate account.
Ref: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html

How to manage acces control at login

I been looking for control what a kind of user can see in my app, this is a scholar project. I'm using Swift and Firebase Authentication. I have two kinds of users: Model and Client. In my app I have some views for the Model and other for the Client. What I want to do is that once they log in, in to the app show just the views for their kind of user. I don't know how to verify if the user that is trying to sign in is a Model or a Client.
#IBAction func signInTapped(_ sender: UIButton) {
if validateFields(){
Auth.auth().signIn(withEmail: emailTxt.text!, password: passTxt.text!, completion:{
(user, error) in
if let u = user {
//User is found
}else{
//Error
}
})
}
}
I know that the code need to be where is the comment "User is found" but I don't know if I need to modify something into the the Firebase Console
Create a Firebase Database or Firestore to your project.
Now when you authenticate a user you should also create a userobject in your databse. In this object you can create a field to store whether your user is a model or a client.
Now once the user has signed in, you can download this userobject from the database, check whether the user is a model or client, and send the user to their corresponding views.
You can use custom claims.
You set them using the Admin SDK
// Javascript
admin.auth().setCustomUserClaims(uid, {model: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
Then in client SDK just read the claim.
user.getIDTokenResult(completion: { (result, error) in
guard let model = result?.claims?["model"] as? NSNumber else {
// Something
}
if model.boolValue {
// Show something else
} else {
// Something else again
}
})
Shamelessly copied from Firebase Docs

How can I change the bearer token in Moya

the documentation shows how to make targets require bearer tokens, which I did like this
extension MyService: AccessTokenAuthorizable {
var authorizationType: AuthorizationType {
switch self {
case .resetPassword, .postTextBook, .bookmarkBook, .getBookmarks, .logout, .verify:
return .bearer
default:
return .none
}
}
}
then it shows how to add tokens to the providers, which I did like this
let token = "abc123"
let authPlugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider<MyService>(plugins: [authPlugin])
but when the token expires, how can I change the token? and does Moya offer a way to automate this process, where if I get a forbidden http response (meaning I am not authorized), it automatically requests a token?
The implementation details of authentication/authorization can be quite different for each API out there. This is the reason why Moya will not handle the auth for you.
That said, implementing your own authentication/authorization can be done in many ways. It will depend on your constraints and/or preferences. As of today, you can find a few solutions sparsely outlined in Moya documentation:
Use the PluginType to add your auth to the requests. But think that this can potentially be used to refresh the token if needed. You may also need to intercept the completion of the request to detect authorization errors and apply your preferred recovery scenario (eg. refresh the token and retry the call).
Same can be implemented using the endpointClosure and/or requestClosure.
You can also consider implementing Alamofire's RequestAdapter and RequestRetrier. Depending on your needs, this can make retries easier. However, on them you will not have straightforward access to your TargetType, so you may need to find a way to recognize the different auth methods needed (ie. your bearer or none).
A few direct references to their documentation:
Plugins
Endpoints
Authentication
Alamofire Automatic Validation
Also, I highly encourage anybody to learn/get inspiration from Eilodon's Networking source code.
for change/refresh token i used this
static func send(request: TargetType) -> PrimitiveSequence<SingleTrait, Response> {
return provider.rx.request(request)
.retry(1)
.observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default))
.filterSuccessfulStatusAndRedirectCodes()
.retryWhen({ (errorObservable: Observable<Error>) in
errorObservable.flatMap({ (error) -> Single<String> in
if let moyaError: MoyaError = error as? MoyaError, let response: Response = moyaError.response {
if **check forbidden http responses here** {
return provider.rx.request(.refreshToken(*your refresh token here*))
.filterSuccessfulStatusCodes()
.mapString(atKeyPath: "*json path to new access token*")
.catchError { (_) in
logout()
throw error
}
.flatMap({ (newAccessToken) -> PrimitiveSequence<SingleTrait, String> in
changeAccessToken()
return Single.just(newAccessToken)
})
}
}
throw error
})
})
}
static func logout() {
// logout action
}
static func changeAccessToken() {
// set new access token
}

Making public realms on Realm Object Server in Swift

I am trying to make a public realm that all users will have read permissions to. The realm team mentioned this capability in this webinar, but I am having trouble finding any documentation on how to do it.
Here is a nice image from the webinar that illustrates the types of realms that can be made in the object server. I am having trouble finding out how to make a public realm.
Here are directions for doing what you want. Be aware that this feature has changed slightly since that webinar was presented. (We also need to improve our documentation, so thank you for asking this question!)
Create an admin user. You can do this by creating a regular user, going to the web dashboard, and editing the user so that it is an administrator. If the user is an admin, upon logging in its isAdmin property should be true.
Using your admin user, open a global Realm. A global Realm is one whose path doesn't contain ~, for example /myGlobalRealm versus /~/eachUserHasTheirOwnCopy. Note that this Realm is completely inaccessible to other users by default. If the Realm doesn't yet exist, the Realm Object Server will automatically create it. This will be your public Realm.
Create an RLMSyncPermissionValue to grant read permissions to all users. This means specifying the path to your public Realm (/myGlobalRealm), as well as a user ID of *.
Then call -[RLMSyncUser applyPermission:callback:] on your admin user with your new permission value, and ensure that the server properly set the permission.
Try opening your public Realm using a different user, and make sure it works.
I hope this helps.
Swift Solution
You can run this once in your simulator to create a global realm with default read/write permissions.
SyncUser.logIn(with: .usernamePassword(username: "ADMIN USERNAME", password: "ADMIN PASSWORD!"), server: URL(string: "Your Server URL")! { (user, error) in
guard user != nil else{
print(error)
return
}
do{
let globalRealm = try Realm(configuration: Realm.Configuration(syncConfiguration: SyncConfiguration(user: user!, realmURL: URL(string: "realm://<YOUR SERVER>:9080/GlobalRealm")!), readOnly: false))
}catch{
print(error)
}
let permission = SyncPermissionValue(realmPath: "/GlobalRealm", userID: "*", accessLevel: SyncAccessLevel.write)
user!.applyPermission(permission) { error in
if let error = error{
print(error)
}else{
user!.retrievePermissions { permissions, error in
if let error = error {
print("error getting permissions")
}else{
print("SUCCESS!")
}
}
}
}
}
Here is a server function to write a public realm:
const Realm = require('realm');
const server_url = 'realm://<Your Server URL>:9080'
const realmName = 'PublicRealm'
const REALM_ADMIN_TOKEN = "YOUR REALM ADMIN TOKEN"
const adminUser = Realm.Sync.User.adminUser(REALM_ADMIN_TOKEN);
var newRealm = new Realm({
sync: {
user: adminUser,
url: server_url + '/' + realmName
},
});
Paste the code into the function editor in the realm dashboard and run the function to create a public realm. You can modify the public realm by changing the properties in the realm constructor.

ServiceStack OAuth - registration instead login

In servicestack OAuth implementation I only saw possibility to automatically login with eg. facebook account.
But is there abbility to support registration process with facebook login. What I wanted is to let users login to facebook app, and then load their Name, Surname and email and prefill needed text boxes for real registration on my site (since I also have to have mobile phone verification etc.) I don't want user to be authorized and authenticated when he logs in with facebook. Only credentials login should be valid one for full site access.
Edit: I found a solution.
In FacebookProvider.cs
public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName)) return false;
}
return tokens != null && session.UserName!=null && !string.IsNullOrEmpty(tokens.AccessTokenSecret);
}
The catch was the && session.UserName!=null part. So we can check if user is logged in using credentials, this will be !=null and user can use all services. If not, this will be ==null and he can only get facebook info from session.
The SocialBootstrap API project shows an example of handling the callback after a successful Authentication by overriding the OnAuthenticated() hook of its custom user session:
I've pulled out, rewrote some and highlighted some of the important bits:
public class CustomUserSession : AuthUserSession
{
public override void OnAuthenticated(IServiceBase authService,
IAuthSession session,
IOAuthTokens tokens,
Dictionary<string, string> authInfo)
{
base.OnAuthenticated(authService, session, tokens, authInfo);
//Populate matching fields from this session into your own MyUserTable
var user = session.TranslateTo<MyUserTable>();
user.Id = int.Parse(session.UserAuthId);
user.GravatarImageUrl64 = CreateGravatarUrl(session.Email, 64);
foreach (var authToken in session.ProviderOAuthAccess)
{
if (authToken.Provider == FacebookAuthProvider.Name)
{
user.FacebookName = authToken.DisplayName;
user.FacebookFirstName = authToken.FirstName;
user.FacebookLastName = authToken.LastName;
user.FacebookEmail = authToken.Email;
}
else if (authToken.Provider == TwitterAuthProvider.Name)
{
user.TwitterName = authToken.DisplayName;
}
}
//Resolve the DbFactory from the IOC and persist the user info
using (var db = authService.TryResolve<IDbConnectionFactory>().Open())
{
//Update (if exists) or insert populated data into 'MyUserTable'
db.Save(user);
}
}
//Change `IsAuthorized` to only verify users authenticated with Credentials
public override bool IsAuthorized(string provider)
{
if (provider != AuthService.CredentialsProvider) return false;
return base.IsAuthorized(provider);
}
}
Basically this user-defined custom logic (which gets fired after every successful authentication) extracts data from the UserSession and stores it in a custom 'MyUserTable'.
We've also overridden the meaning of IsAuthorized to only accept users that have authenticated with CredentialsAuth.
You can use this data to complete the rest of the registration.
Other possible customizations
ServiceStack's built-in Auth persists the AuthData and populates the Session automatically for you. If you want to add extra validation assertions you can simply use your own custom [Authentication] attribute instead containing additional custom logic. Look at the implementation of the built-in AuthenticateAttribute as a guide.