How do I pass the id of the logged in user to my chosen controller in grails using acegi - authentication

I have secured my Grails app using the ACEGI plugin and am using annotations on my controller methods to prompt the user to login.
My app has a static HTML front page with a login link on it which redirects to the login/auth page. On a successful login I want to load my own custom page for the authenticated user, called person/mainpage.
In my LoginController there is the following code...
def index = {
if (isLoggedIn()) {
redirect uri: '/'
}
else {
redirect action: auth, params: params
}
}
/**
* Show the login page.
*/
def auth = {
nocache response
if (isLoggedIn()) {
redirect uri: '/'
return
}
String view
String postUrl
def config = authenticateService.securityConfig.security
if (config.useOpenId) {
view = 'openIdAuth'
postUrl = "${request.contextPath}/login/openIdAuthenticate"
}
else if (config.useFacebook) {
view = 'facebookAuth'
postUrl = "${request.contextPath}${config.facebook.filterProcessesUrl}"
}
else {
view = 'auth'
postUrl = "${request.contextPath}${config.filterProcessesUrl}"
}
render view: view, model: [postUrl: postUrl]
}
This redirects the successful login back to the main page of the application (/), which is not what I want. Googling for a little while I found that I could define a default target for my authentication in securityconfig.groovy like this..
defaultTargetUrl = "/person/mainpage"
My question is how to identify which user logged in when I land on my mainpage action in my PersonController?
At first I changed my index action in LoginController to redirect to my page like this...
def index = {
if (isLoggedIn()) {
redirect controller: person, action: mainpage, params: params
}
else {
redirect action: auth, params: params
}
}
but the id of the logged in person does not appear in the params (which I think I am happy about because it seems crazy to be able to pull up pages just by defining a user row ID as a url parameter).
So what's the right way to do this? Basically I want my person/mainpage action to be able to resolve the currently logged in user.

You can access the logged in user using authenticateService. To get the user/person domain instance call authenticateService.userDomain() and to just get the Authentication (which has a getUsername() method that might be sufficient) call authenticateService.principal(). If your defaultTargetUrl is "/person/mainpage" then your PersonController's 'mainpage' action would look something like this:
class PersonController {
def authenticateService
def mainpage = {
def user = authenticateService.userDomain()
if (user) {
log.info "you're logged in as $user.username"
}
else {
log.info "you're not logged in"
}
[user: user]
}
}
and then you'd have the 'user' available in mainpage.gsp to render data from.

Related

Vue + MSAL2.x + Azure B2C Profile Editing

First, I am not finding Vue specific examples using MSAL 2.x and we'd like to use the PKCE flow. I am having issues with the way the router guards are run before the AuthService handleResponse so I must be doing something wrong.
In my main.js I am doing this...
// Use the Auth services to secure the site
import AuthService from '#/services/AuthServices';
Vue.prototype.$auth = new AuthService()
And then in my AuthConfig.js I use this request to login:
loginRequest : {
scopes: [
"openid",
"profile",
process.env.VUE_APP_B2C_APISCOPE_READ,
process.env.VUE_APP_B2C_APISCOPE_WRITE
]
},
The docs say it should redirect to the requesting page but that is not happening. If user goes to the protected home page they are redirected to login. They login, everything is stored properly so they are actually logged in, but then they are sent back to the root redirect URL for the site, not the Home page.
When a user wants to login we just send them to the protected home page and there is a login method called in the router guard which looks like this:
router.beforeEach(async (to, from, next) => {
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
const IsAuthenticated = await Vue.prototype.$auth.isAuthenticated()
console.log(`Page changing from ${from.name} to ${to.name}, requiresAuth = ${requiresAuth}, IsAuthenticated = ${IsAuthenticated}`)
if (requiresAuth && !IsAuthenticated)
{
next(false)
console.log('STARTING LOGIN')
Vue.prototype.$auth.login()
// Tried this
// Vue.prototype.$auth.login(to.path)
} else {
next()
}
})
In AuthServices.js I have this...
// The user wants to log in
async login(nextPg) {
// Tell B2C what app they want access to and their invitation ID if they are new
if (store.getters.userEmail != null) {
aCfg.loginRequest.loginHint = store.getters.userEmail
}
aCfg.loginRequest.state = "APP=" + store.getters.appCode
if (store.getters.appointmentLink != null && store.getters.appointmentLink != '') {
aCfg.loginRequest.state += ",ID=" + store.getters.appointmentLink
}
// Tried this
// if (nextPg && nextPg != '') {
// aCfg.loginRequest.redirectUrl = process.env.VUE_APP_B2C_REDIRECT_URL + nextPg
// }
return await this.msalInst.loginRedirect(aCfg.loginRequest)
}
I tried puting a nextPg parameter in the login method and adding a redirectUrl property to the login request but that gives me an error saying it is not one of the configured redirect URLs.
Also, I'm trying to make the user experience better when using the above technologies. When you look at the MSAL2.x SPA samples I see that when returning from a Profile Edit, a user is logged out and they are required to log in again. That sounds like a poor user experience to me. Sample here: https://github.com/Azure-Samples/ms-identity-b2c-javascript-spa/blob/main/App/authRedirect.js
Do I need to just create my own profile editing page and save data using MSGraph to prevent that? Sorry for the noob questions. Ideas?
Update - My workaround which seems cheesy is to add these two methods to my AuthService.js:
storeCurrentRoute(nextPath) {
if (!nextPath) {
localStorage[STOR_NEXT_PAGE] = router.history.current.path
} else {
localStorage[STOR_NEXT_PAGE] = nextPath
}
console.log('Storing Route:', localStorage[STOR_NEXT_PAGE])
}
reEstablishRoute() {
let pth = localStorage[STOR_NEXT_PAGE]
if (!!pth && router.history.current.path != pth) {
localStorage[STOR_NEXT_PAGE] = ''
console.log(`Current path is ${router.history.current.path} and reEstablishing route to ${pth}`)
router.push({ path: pth })
}
}
I call storeCurrentRoute() first thing in the login method and then in the handleResponse() I call reEstablishRoute() when its not returning from a profileEdit or password change. Seems like I should be able to make things work without this.
Update Number Two - When returning from B2C's ProfileEdit User Flow the MSAL component is not logging me out properly. Here is my code from my handlePolicyChange() method in my AuthService:
} else if (response.idTokenClaims[clmPolicy] === aCfg.b2cPolicies.names.editProfile) {
Vue.nextTick(() => {
console.log('BACK FROM Profile Change')
Vue.prototype.$swal(
"Success!",
"Your profile has been updated.<br />Please log in again.",
"success"
).then(async () => {
this.logout()
})
})
}
:
// The user wants to log out (all accounts)
async logout() {
// Removes all sessions, need to call AAD endpoint to do full logout
store.commit('updateUserClaims', null)
store.commit('updateUserEmail', null)
let accts = await this.msalInst.getAllAccounts()
for(let i=0; i<accts.length; i++) {
const logoutRequest = {
account: accts[i],
postLogoutRedirectUri: process.env.VUE_APP_B2C_REDIRECT_URL
};
await this.msalInst.logout(logoutRequest);
}
return
}
It is working fine until the call to logout() which runs without errors but I looked in my site storage (in Chrome's debug window > Application) and it looks like MSAL did not clear out its entries like it does on my normal logouts (which always succeed). Ideas?
As part of the MSAL auth request, send a state Parameter. Base64 encode where the user left off inside this parameter. MSAL exposes extraQueryParameters which you can put a dictionary object inside and send in the auth request, put your state Key value pair into extraQueryParameters.
The state param will be returned in the callback response, use it to send the user where you need to.

Authenticate with Moodle from a mobile app

My mobile app needs to log in to Moodle to get Json data from a webservice and display it using Angular.
In order to do that, I need to pass in a username and password and get a Moodle webservice token back, so my app doesn't need to log in again (at least until the token expires).
(this is one of those "ask and answer your own question" things, so my solution is below, but comments & suggestions welcome.)
With thanks to all the other StackOverflow pages I have used to create this solution!
See also - how to get data from your Moodle webservice with Angular.
Step 1. Check if a token already exists
jQuery(document).ready(function () {
/* when the user clicks log-out button, destroy the session */
$('#btn_logout').on('click', function () {
$('.pane').hide(); /* hide all screens */
$('#menu').toggleClass('ui-panel-open ui-panel-closed');
$.jStorage.deleteKey('session');
makeUserLogin();
});
var session = $.jStorage.get('session', ''); // syntax: $.jStorage.get(keyname, "default value")
if (session) { // if there is already a session, redirect to landing pane
showApp();
} else { // if there is no session *then* redirect to the login pane
makeUserLogin();
}
});
Step 2. create functions to show app & redirect to login page
function showApp() {
$('#home-pane').show(); /* show home screen */
$('#system-message').hide();
$('#login-pane').hide(); /* hide login screen*/
$('#menu_btn').removeClass('hidden'); /* show menu button so user can see rest of app */
}
function makeUserLogin() {
$('#btn_login').click(function () {
console.log('click event for login_button');
var username = $('#username').val();
var password = $('#password').val();
postCredentials(username, password, createSession);
});
$('#menu_btn').addClass('hidden'); /* hide menu button so user cannot see rest of app */
$('#home-pane').hide(); /* hide home screen */
$('#login-pane').show(); /* show login screen */
}
function postCredentials(username, password, callback) {
if ((username.length && password.length) && (username !== '' && password !='')) {
var url = 'https://moodle.yourcompany.com/local/login/token.php';
$.post(url, {
username: username,
password: password,
service: 'webservice_ws' // your webservice name
}).done(function (data) {
token = data.token;
dataString = JSON.stringify(data);
if (dataString.indexOf('error') > 0) {
showErrorDialog('<p class="error">Invalid user credentials, please try again</p>');
}
else {
createSession(token);
}
}).fail(function () {
showErrorDialog('<p class="error">Login failed</p>');
});
} else {
showErrorDialog('<p class="error">Please enter a username and password</p>');
}
}
function createSession(token) {
// syntax: $.jStorage.set('keyname', 'keyvalue', {TTL: milliseconds}); // {TTL... is optional time, in milliseconds, until key/value pair expires}
$.jStorage.set('session', token, { TTL: 28800000 });
// redirect to whatever page you need after a successful login
showApp();
}
function showErrorDialog(errorMsg) {
$('#system-message').html(errorMsg);
$('#system-message').fadeIn();
}

Pass data to component after it has finished routing

I have a navbar-login (login form in navbar) and a page-login (a page with login-form which can be routed to). The navbar-login-form and the page-login-form are two-way-binded via a service (see first codebit below).
What I want is the following flow:
User enters email and password in navbar-login
On Clicking Submit Button, the credentials are sent to a login.service
If credentials are wrong, the service routes to the login-page with the credentials displayed
The two-way-binding with the service works fine if the page-login is already displayed. But if it wasn't displayed and I enter credentials and hit the button, it only routes to page-login but does not pass the credentials.
I'm using the following service to have navbar-login and page-login communicate with each other (two-way-binding across "unrelated" components):
export class LoginNav2PageService {
viewerChange: EventEmitter<{email:string,password:string}> = new EventEmitter();
constructor() {}
emitNavChangeEvent(user:{email:string,password:string}) {
this.viewerChange.emit(user);
}
getNavChangeEmitter() {
return this.viewerChange;
}
}
This is the navbar component, pass2page is hooked with a keyup event in the HTML inputs:
export class LoginNavbarComponent {
user:= {email:'',password:''};
constructor(private _loginNav2pageService:LoginNav2PageService, private _loginService:LoginService){}
pass2page(){
this._loginNav2pageService.emitNavChangeEvent(this.user);
}
onNavbarLoginBtn(){
this._loginService.onNavbarLogin(this.user);
}
}
And this is the listener in the page-login component:
export class LoginPageComponent implements OnInit{
user= {email:"", password:""};
subscription:any;
constructor(private _loginNav2pageService:LoginNav2PageService){}
ngOnInit():any{
this.subscription = this._loginNav2pageService.getNavChangeEmitter().subscribe(user => this.setViewer(user));
}
setViewer(user:{email:string, password:string}){
this.user = user;
}
}
And finally the loginService:
export class LoginService{
constructor(private _router:Router, private _loginNav2pageService:LoginNav2PageService){}
//login User from Navbar
onNavbarLogin(user:LoginUserInterface){
//login and routing if successful
if(user.email === 'name' && user.password === '1234'){
console.log("Login Success");
//route to platform
}
//else route to login page to show validation errors to users
else {
this._router.navigate(['Login']);
this._loginNav2pageService.emitNavChangeEvent(user);
console.log("wrong credentials");
}
}
}
after a good nights sleep I figured it out :), the code above was a snippet i adapted before in some other parts and way to complicated for this....
Now i'm simply using the ngAfterViewInit Lifecycle Hook to get the data from the service.
Thanks!

mithril.js redirect user if not connected

How to use mithril.js and it's routing system to redirect my user to the login form when the user is not yet connected?
I define some routes:
m.route.mode = 'search';
m.route(document.getElementById('app'), "/", {
'/': LoginForm,
'/orders': OrderList,
'/order/new': OrderForm,
'/order/:orderId': OrderForm
});
But I want to redirect /orders and other routes to / if the user has not login.
Do I need to put the code in the controllers (OrderList.controller and OrderForm.controller)? or in the views (OrderList.view and OrderForm.view)?
window.OrderForm = {
controller: function () {
var ctrl = this
if (/* user not logged */) {
m.route('/')
return;
}
// controller code
},
view: function(ctrl) {
if (/* user not logged */) {
m.route('/')
return;
}
// view code
return m('....');
}
}
You should put redirect to the controllers.
In this case redirect is called before requestAnimationFrame, so you are redirected to appropriate controller and appropriate view is rendered.
In case redirecting in the views, you probably are not redirected properly, view is rendered anyway.

Laravel Auth loging out malicious user

I'm implementing a banning system. I have an admin view where the admin can ban users. I want to log out a user if the admin bans him in that moment. Something like Auth::logout($user). Is that possible?? Or do I have to add a filter to all my routes to check if the logged user is banned.
Filters are the way to go. It's easy and clean to solve this problem, see my example below.
if user is banned at any point it will logout user at his/her next request, you can redirect user with Session flash message, your login code works as it is.
Route::filter('auth', function()
{
if (Auth::guest())
{
if (Request::ajax())
{
return Response::make('Unauthorized', 401);
}
else
{
return Redirect::guest('login');
}
}
else
{
// If the user is banned, immediately log out.
if(Auth::check() && !Auth::user()->bannedFlag)
{
Auth::logout();
Session::flash('message','Your account is banned, please contact your administrator to active your account');
// redirect to login page
return Redirect::to('/');
}
}
});
and for your routes use group routes
Route::group(array('before' => 'auth'), function()
{
//use your routes
});