I've recently started with vapor4 (didn't use any older version) and I'm trying to figure out how to implement user authorization and authentication. While i understand basic concepts, having worked with Laravel before I still can't figure out what to do in vapor.
I extended my User with. Ik there is no pw hashing, this is for testing and basic understanding. We'll ignore that for now.
extension User: ModelAuthenticatable
{
static let usernameKey = \User.$name
static let passwordHashKey = \User.$password
func verify(password: String) throws -> Bool {
return password == self.password
}
}
The problem is i can't find a tutorial how to use this authentication. I just kind of try stuff to get it to work, but to no success. This is in my routes file.
let auth = app.grouped(User.authenticator())
auth.get("sign-in") { req in
"I'm authenticated"
}
My first goal would just be to receive a success or failure answer when trying this route. Ultimately i want to switch to a token based solution but one step at a time.
stuff i read was: https://docs.vapor.codes/4.0/authentication/ and https://theswiftdev.com/all-about-authentication-in-vapor-4/. Anyway i couldn't quit figure it out how to use the described authenticators.
While writing this i finally figured it out. Anyway for people stumbling on this. It's as easy as this:
let auth = app.grouped(User.authenticator(), User.guardMiddleware())
auth.get("sign-in") { req in
"I'm authenticated"
}
Your user-class offers a guardMiddleware by default. You don't have to implement anything else, just use it in your route.
Related
In an iOS project, we decided to turn on a feature as a demo purposing scenario to only some white listed users to implement this, after user logs in to the app in LoginController we send user id to Firebase by using this
Analytics.setUserProperty(id, forName: "ID")
and we have setup a remote config flag with multiple conditions which each condition is checking a particular user id if that's equal with the user id sending after login process to Firebase, it returns true otherwise false.
Exactly after we send user id in LoginController we try to fetch the remote config flag
RCUtility.fetchAVFlagValue { result in
UserDefaults.standard.set(result, forKey: DefaultKeys.remoteConfigFlag)
}
and here is the RCUtility class
class RCUtility {
static func fetchAVFlagValue(completion: #escaping (_ flag: Bool) -> Void) {
#if DEBUG
let fetchDuration: TimeInterval = 0
activateDebugMode()
#else
let fetchDuration: TimeInterval = 3600
#endif
let remoteConfig = RemoteConfig.remoteConfig()
remoteConfig.fetch(withExpirationDuration: fetchDuration) { _, error in
if let error = error {
print("Error fetching remote values: \(error)")
return
}
remoteConfig.activateFetched()
completion(remoteConfig
.configValue(forKey: "auto_vision_flag")
.boolValue)
}
}
static func activateDebugMode() {
let debugSettings = RemoteConfigSettings(developerModeEnabled: true)
RemoteConfig.remoteConfig().configSettings = debugSettings
}
}
the problem is as long as I test it in debug mode which time interval is 0 it works properly when for example I login as a whitelisted user I see that specific feature and when I logout and login as another none whitelisted users I won't see the feature but when we publish the app for QAs this isn't happening if they login as a good user firstly they see the feature but if they logout and login again as a bad user they can still see the feature which they suppose not! unless they wait for 1 hr (the specified time interval to fetch a new value for the flag) or if they delete the app and re-install it and login again! It seems FB doesn't return updated value if these two conditions aren't met.
I am sure the flag has been setup in Firebase correctly.
I have tried different ways such as activating the flag after fetching from FB or try to reset the value from UserDefaults after user logs out of the app or save the value in a constant instead of using UserDefaults but none of them worked I am not sure is it the way FB works with remote config flag or I am missing anything?
The Android version doesn't have this issue with same implementation and same time interval and regardless of the debug or production mode or type of user they get the correct value each time from Firebase without having to delete and reinstall the app.
Remote Config's fetch mechanism probably doesn't know anything about the user, which means that it simple waits until the cache expires, which defaults to 12 hours. Also see the Firebase documentation on caching.
I'm inclined to consider this a bug, although I'm not sure if it's working as intended. Either way, it might be worth it to file a bug report.
As a workaround, you could tell fetch to request new values after you detect a fresh login by calling fetchWithExpirationDuration:completionHandler: with a very low expiration duration. Just be sure to only call this variant after a new user signs in, as too frequent calls may result in a server-side throttle being applied.
I've done some tests and I believe it's a Firebase Bug. In the first request I get the old result, and after two requests I got the update.
I am using IDP provider for authentication and trying to bypass the standard keycloak login screen (so I need to go immediately to the IDP specific authorization screen). According to this documentation https://keycloak.gitbooks.io/server-adminstration-guide/content/topics/identity-broker/suggested.html we can simply provide idpHint for this. Though that doesn't work.
let keycloakAuth : any = new Keycloak('keycloak.json');
keycloakAuth.createLoginUrl({idpHint: 'ad-oidc'});
It failed with
Uncaught TypeError: Cannot read property 'redirectUri' of undefined
at Keycloak.kc.createLoginUrl (keycloak-core.js:212)
As far as I understand that's because adapter is not created yet. So probably we need to pass this option sometimes later (but not sure at which phase).
I was able to do this only by hardcoding the idpHint inside of the keycloak-core.js itself temporarily. Looking forward to avoid this.
Thanks in advance.
I solved the problem like this:
let kc = Keycloak('/backend/frontend.json');
let kcLogin = kc.login;
kc.login = (options) => {
options.idpHint = 'some-hint';
kcLogin(options);
};
kc.init({ onLoad: 'login-required' }).success((authenticated) => {
console.log('Logged in!');
}});
This way the very first login attempt already uses the hint. I somehow don't get, why there is no simple way to use the idpHint option, the inceptor is the only way I found to use it, without directly patching the keycloak.js file.
It would be perfect, if the Keycloak constructor just would accept an idpHint option.
You have to initialize the keycloak object first.
You don't need to call createLoginUrl(), you need to call login(options) which by its turn going to call createLoginUrl.
var keycloak = new keycloak(JSON);
keycloak.init().success(function(authenticated){
if(authenticated){
console.log("logged in");
}
else{
keycloak.login({idpHint:'ad-oidc'});
}
}).error(function(){
console.log("failed to initialize");
});
Sorry there might be syntax error but I hope it explains how to do it.
For the past 3 years we have used HTML/Js only with Firebase but now we are using Unity as well.
The current Unity/Firebase only works on Android/iOS when deployed and 99% of our work is on the windows store.
I've actually got a pretty decent Unity/Firebase codebase going but it requires me to use a full App Secret.
All the other libraries expose a method to login with Email/Password but the REST API only allows the use of a token or your app secret that it then states is ill advised to put into your client; I guess the thinking is if you're using a different library that you'll have your own auth/user method which we don't...
Now, I've pulled apart the web version and got this:
https://auth.firebase.com/v2/<myfirebase>/auth/password?&email=dennis%40<mysite>&password=<mypassword>v=js-2.2.9&transport=json&suppress_status_codes=true
So there IS an endpoint that I can send stuff to and I've tested it inside unity with good results.
Obviously the URL isn't guaranteed to stay working but I'm wondering if there is any reason NOT to use this?
Also, Why not just expose this endpoint in the official REST API?
As I understand it, that URL will continue to work for your Legacy Firebase project. You will have to do the same sort of reverse engineering if you want to update to the new Firebase 3.0 API. However, if you are still using a legacy Firebase project -- I encourage you to take a look at this. It has not been updated to work with Firebase 3.0 -- so I needed to do something similar to what you did to allow login to the new API.
I was able to do this with the new API using C# as follows (where FirebaseManager is a Singleton I wrote for Global variables and functions to write and read from/to the DB :
Hashtable loginData = new Hashtable();
loginData.Add ("email", <EMAIL-GOES-HERE>);
loginData.Add ("password", <PASSWORD-GOES-HERE>);
loginData.Add ("returnSecureToken", true);
UnityHTTP.Request loginRequest = new UnityHTTP.Request ("post",
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key="
+ <YOUR-PROJECT-API-KEY-GOES-HERE>, loginData);
loginRequest.Send ((request) => {
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error logging in. Server returned null or malformed response");
}
FirebaseManager.Instance.idToken = (string)jsonResponse["idToken"]; // This is your auth token
FirebaseManager.Instance.uid = (string)jsonResponse["localId"]; // this is your "uid"
});
// I have a list of users in my db keyed by the "uid" -- I access them like this
UnityHTTP.Request fullnameRequest = new UnityHTTP.Request ("get",
<YOUR-DATABASE-ROOT-URL-HERE>
+ "/users/" + FirebaseManager.Instance.uid + ".json?auth=" + FirebaseManager.Instance.idToken);
fullnameRequest.Send ((request) => {
Debug.Log(request.response.Text);
Hashtable jsonResponse = (Hashtable)JSON.JsonDecode(request.response.Text);
if (jsonResponse == null) {
DisplayErrorMessage("Error getting user info. Server returned null or malformed response");
}
FirebaseManager.Instance.fullname = (string)jsonResponse["fullname"];
FirebaseManager.Instance.groupId = (string)jsonResponse["group"]; // just storing this in memory
});
So I don't think there is any harm in using the URL, just make sure you budget time for more work when things change.
Currently using grails 2.2.2
I've been trying to implement tokens into my application and have come up with this issue. We try to avoid re-rendering pages because it can be very slow so we return JSON instead. The following is a basic controller call that we use but I'm not sure what I should be doing to reset/get a new token.
public saveThing(ThingCommand cmd) {
Map model = [:]
withForm {
try {
thingService.saveThing(cmd)
model.success = true
} catch (Exception e) {
model.error = true //any validation errors or anything else
// RESET TOKEN HERE/GET NEW TOKEN?
}
}.invalidToken {
model.invalidToken = true
}
render model as JSON
}
From my understanding the token is thrown away once the withForm closure is executed. This causes an issue since I don't actually re-render the form which seems to be the normal way of generating a new token. How could I do this manually or is there an easier way to do this (plugin?)
Thanks!
Form tokens through withForm are not designed to be used with AJAX requests. They are designed to be used with HTML forms and POST requests which re-render the form and generate a new token for the form.
In order to make them work with JSON/AJAX requests you will need to implement your own token generation when you process the request and reject it. A good starting place would be to look at the old tests which test withForm. This should give you an idea on how tokens are created and stored.
I am developing a vaadin-based project using Apache Shiro 1.2 for security. I have a problem with 'remember me' feature. I try to use CookieRememberMeManager as RememberMeManager, but after authentification Subject.isRemembered() always returns false.
public class ApplicationSecurityManager extends DefaultSecurityManager {
public ApplicationSecurityManager(Realm singleRealm) {
super(singleRealm);
setRememberMeManager(new CookieRememberMeManager());
}
}
I set SecurityManager in init method of GuiceFilter.
final Realm realm = new ApplicationSecurityRealm();
final SecurityManager securityManager = new ApplicationSecurityManager(realm);
SecurityUtils.setSecurityManager(securityManager);
When I try to login to my application, all works fine except 'remember me' feature.
Code:
final Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
token.setRememberMe(rememberMe);
currentUser.login(token);
Application have no exceptions, and i could't resolve this problem using debug.
I use Apache Tomcat 7.0.40, can it to forbid cookies?
P.s. Sorry for my English, I'm not from an English-speaking country.
I realize it has been a year, but this question is getting a fair number of views, so I thought I'd post some information.
Subject.isRemembered() is a little tricky in Shiro. It only returns true if the Subject has a valid Remember Me setting (cookie, etc) AND the Subject is not Authenticated. Details here: http://shiro.apache.org/static/1.2.2/apidocs/org/apache/shiro/subject/Subject.html#isRemembered()
So, I suspect that your Remember Me is working fine, but your expectations for Subject.isRemembered() doesn't match what the method actually does.