AngularFireAuthGuard redirectUrl after login - angularfire2

I use firebase and AngularFireAuthGuard to protect specific routes, so that only authenticated users are allowed to access them.
In particular, my MainComponent and MgmtComponent should only be accessible to AUTHENTICATED users.
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['/login']);
const routes: Routes = [
{ path: 'teams/:teamId/sessions/:sessionId',
component: MainComponent,
canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin }
},
{ path: 'mgmt',
component: MgmtComponent,
canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin }
},
{
path: 'login',
component: LoginComponent
}
];
My Problem is, that the user is not redirected back to the originally requested URL, after a successful login.
So what I want/expect is:
user goes to /mgmt
as the user is not authenticated he is automatically redirected to /login
user authenticates (e.g. via google or Facebook OAuth)
user is automatically redirected back to the originally requested page (/mgmt)
Steps 1-3 work fine, but step 4 is missing.

Now that the feature request is in, you can do this using the auth guard. However, the docs are unclear, so here is how I did it.
/** add redirect URL to login */
const redirectUnauthorizedToLogin = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
return redirectUnauthorizedTo(`/login?redirectTo=${state.url}`);
};
/** Uses the redirectTo query parameter if available to redirect logged in users, or defaults to '/' */
const redirectLoggedInToPreviousPage = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
let redirectUrl = '/';
try {
const redirectToUrl = new URL(state.url, location.origin);
const params = new URLSearchParams(redirectToUrl.search);
redirectUrl = params.get('redirectTo') || '/';
} catch (err) {
// invalid URL
}
return redirectLoggedInTo(redirectUrl);
};

This is an open feature request, the angularfire team is working on it: https://github.com/angular/angularfire/pull/2448
Meanwhile I found this workaround:
In the app-routing-module.ts instead of
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['/login']);
I use following to store the url in the sessionStorage:
const redirectUnauthorizedToLogin = (route: ActivatedRouteSnapshot) => {
const path = route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/');
return pipe(
loggedIn,
tap((isLoggedIn) => {
if (!isLoggedIn) {
console.log('Saving afterLogin path', path);
sessionStorage.setItem('afterLogin', path);
}
}),
map(loggedIn => loggedIn || ['/login'])
);
};
In the LoginComponent I read the value from the session storage to redirect:
sessionStorage.getItem('afterLogin');
this.router.navigateByUrl(redirectUrl);

Related

Strapi returns 404 for custom route only when deployed to Heroku

I have created a custom route in Strapi v4 called "user-screens". Locally I hit it with my FE code and it returns some data as expected. However when I deploy it to Heroku and attempt to access the endpoint with code also deployed to Heroku it returns a 404. I've tailed the Heroku logs and can see that the endpoint is hit on the server side, but the logs don't give anymore info other than it returned a 404.
I am doing other non custom route api calls and these all work fine on Heroku. I am able to auth, save the token, and hit the api with the JWT token and all other endpoints return data. This is only happening on my custom route when deployed to Heroku. I've set up cors with the appropriate origins, and I am wondering if I need to add something to my policies and middlewares in the custom route. I have verified the permissions and verified the route is accessible to authenticated users in the Strapi admin.
Here is my route:
module.exports = {
routes: [
{
method: "GET",
path: "/user-screens",
handler: "user-screens.getUserScreens",
config: {
policies: [],
middlewares: [],
},
},
],
};
And my controller:
"use strict";
/**
* A set of functions called "actions" for `user-screens`
*/
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
ctx.send(result);
});
},
};
For anyone facing this, the answer was to change how I returned the ctx response from a 'send' to a 'return' from the controller method. I am not sure why this works locally and not on Heroku, but this fixes it:
New controller code:
module.exports = {
getUserScreens: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
return strapi.entityService
.findMany("api::screen.screen", {
owner: user.id,
populate: ["image"],
})
.then((result) => {
return result;
})
.catch((error) => {
return error;
});
},
};

Inject token from auth0 to React-admin Hasura data provider

This example demonstrates how to use Auth0 with react-admin. It is working as expected.
We are trying to adjust it so it will use the hasura data provider. We've created a new file dataProvider.js that will construct the data-provider:
import buildHasuraProvider from "ra-data-hasura";
import { ApolloClient, InMemoryCache } from "#apollo/client";
export const initDataProvider = async (token) => {
const client = new ApolloClient({
uri: process.env.REACT_APP_GRAPHQL_URI,
headers: {
Authorization: `Bearer ${token}`,
},
cache: new InMemoryCache(),
});
const dataProvider = await buildHasuraProvider({ client });
return dataProvider;
};
However, we are missing the JWT token which is created as part of the Auth0 authentication process. We do not know how to get the token in order to initialize the data provider with it. Does react-admin know how to do it on its own? if not, how do we access the JWT token to do it ourselves manually?
This is the authProvider source-code:
import authConfig from "./authConfig";
import {Auth0Client} from '#auth0/auth0-spa-js';
const auth0 = new Auth0Client({
domain: authConfig.domain,
client_id: authConfig.clientID,
redirect_uri: authConfig.redirectURI,
cacheLocation: 'localstorage',
useRefreshTokens: true
});
export default {
// called when the user attempts to log in
login: (url) => {
if (typeof url === 'undefined') {
return auth0.loginWithRedirect()
}
return auth0.handleRedirectCallback(url.location);
},
// called when the user clicks on the logout button
logout: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) { // need to check for this as react-admin calls logout in case checkAuth failed
return auth0.logout({
redirect_uri: window.location.origin,
federated: true // have to be enabled to invalidate refresh token
});
}
return Promise.resolve()
})
},
// called when the API returns an error
checkError: ({status}) => {
if (status === 401 || status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
// called when the user navigates to a new location, to check for authentication
checkAuth: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) {
return Promise.resolve();
}
return auth0.getTokenSilently()
})
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => {
return Promise.resolve()
},
};
It is unclear to us if there is a point where we can extract the token from.
getTokenSilently should give you back the token.
You'll have to structure your React app such that you have access to the result of this method before you construct your data provider.

Okta signout don't work in Angular Applicaiton

I am using OKTA SDK for the angular following enter link description here
this documentation. I am also using OktaCallbackComponent and OktaAuthService for authentication.
I can log in successfully. after a successful login OKTA redirects me to OktaCallbackComponent where they store some keys in localstroge and finally, I get navigated to my main page.
now when I click on the logout button from the application it does not work. As I see it the page loads and immediately navigates to the callback component and again navigates to the main page. whereas I want the login page should come to the user.
this is my logout function.
async logout(){
this.oktaAuth.tokenManager.clear()
await this.oktaAuth.signOut();
this.router.navigate(['/login']);
this.toastr.success('Logout Successfully', 'See you next time' , {timeOut: 5000});
}
can anyone help me with what could be the issue.
{
path: 'main',
component: OpDataTableComponent,
canActivate: [ OktaAuthGuard ],
data: {
title: 'Main Page'
}
},
{
path: CALLBACK_PATH,
component: OktaCallbackComponent,
// Later: Add a component
},
{
path: 'login',
// component: LoginComponent,
component:OktaLoginComponent,
canActivate: [checkAfterLoginService],
data: {
title: 'Login Page'
}
}
CheckAfterLoginService
export class checkAfterLoginService {
constructor(private oktaAuth: OktaAuthService,private tokenService: TokenService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
if(this.oktaAuth.isAuthenticated())
{
return this.router.navigateByUrl('/main');
}else
{
return false;
}
}
}
Okta configuration.
const ISSUER = 'https://...../oauth2/default';
const HOST = window.location.host;
const REDIRECT_URI = 'https://..../callback';
const SCOPES = 'openid profile email';
const config = {
issuer: ISSUER,
clientId: '.....',
redirectUri: REDIRECT_URI,
scopes: SCOPES.split(/\s+/)
};
P.s logout URL added to the application setting is https://../login route.
how can I solve the issue or what could be the issue? your help is much appreciated.
Try changing your logout() method to be as follows:
async logout(){
await this.oktaAuth.signOut();
this.router.navigate(['/login']);
this.toastr.success('Logout Successfully', 'See you next time' , {timeOut: 5000});
}
You're currently clearing the tokens manually, which makes our underlying Auth JS SDK thinking you've already logged out. this.oktaAuth.signOut() should clean up the tokens for you. If you still want to clear them manually, make sure and do it after signOut().
I had similar issues, but I wanted to redirect back to the okta login screen. This is what worked for my situation.
public logout(): void {
const oktaBaseUrl: string = `${environment.okta.issuer}/v1`;
const oktaTokenStorage: any = JSON.parse(localStorage.getItem('okta-token-storage'));
const oktaIdToken = oktaTokenStorage?.idToken;
this.oktaAuth.logout();
window.location.href = `${oktaBaseUrl}/logout?id_token_hint=${oktaIdToken.idToken}&post_logout_redirect_uri=${environment.okta.postLogoutRedirectUri}`;
}

Laravel Sanctum/Vuex Uncaught Error Vue Router Navigation Guard

Can anyone advise on this issue?
I've got a Laravel app with a Vue front-end, connecting to the API using Laravel Sanctum. (within the same application) I'm trying to set up the authentication guards so routes can only be accessed after authentication with the API.
I'm connecting through the state action like so:
async getAuthenticatedUser({ commit }, params) {
await axios.get('api/auth-user', { params })
.then(response => {
commit('SET_AUTHENTICATED', true)
commit('SET_AUTHENTICATED_USER', response.data)
localStorage.setItem('is-authenticated', 'true')
return Promise.resolve(response.data)
})
.catch(error => {
commit('SET_AUTHENTICATED', false)
commit('SET_AUTHENTICATED_USER', [])
localStorage.removeItem('is-authenticated')
return Promise.reject(error.response.data)
})
},
The state authenticated property is set as follows:
const state = () => ({
authenticated: localStorage.getItem('is-authenticated') || false,
authUser: [],
})
I have the following guard checking the auth. If I'm not signed in the app correctly redirects me back to the login screen when I access a route with the requiresAuth attribute.
However when I attempt to log in, I get Redirected when going from "/login" to "/dashboard" via a navigation guard.
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.getters["Auth/isAuthenticated"]) {
next()
return
}
next('/login')
}
if (to.matched.some((record) => record.meta.requiresVisitor)) {
if (! store.getters["Auth/isAuthenticated"]) {
next()
return
}
next('/dashboard')
}
next()
})
If it's safe to ignore, and you are using vue-router ^3.4.0, you can do:
import VueRouter from 'vue-router'
const { isNavigationFailure, NavigationFailureType } = VueRouter
...
this.$router.push(fullPath).catch(error => {
if (!isNavigationFailure(error, NavigationFailureType.redirected)) {
throw Error(error)
}
})

Cannot connect Ember Simple Auth and DRF Token Auth

I have a trouble with Ember Simple Auth.
I'm trying to connect my server-side application, which working on Django 1.9 with DRF, and client-side which working on Ember 2.2.
On server side I'm obtaining token on 'http://localhost:8000/api-token-auth/'. Function requires two args from request: "username" and "password". But Ember Simple Auth send POST request with args: "username[identification]" and "password[password]", and server returns "400". I think that problem with arguments keys.
POST request
Responce
I tried to change .authenticate method in oauth2-password-grant.js(i can't write custom authenticator because i'm newbee in javascript), but nothing changed.
Manually POST request returns expected answer.
Please tell me the way to solve this problem.
And please forgive me for my english.
authenticate(identification, password, scope = []) {
return new RSVP.Promise((resolve, reject) => {
const data = { 'grant_type': 'password', username: identification, password };
const serverTokenEndpoint = this.get('serverTokenEndpoint');
const scopesString = Ember.makeArray(scope).join(' ');
if (!Ember.isEmpty(scopesString)) {
data.scope = scopesString;
}
this.makeRequest(serverTokenEndpoint, data).then((response) => {
run(() => {
const expiresAt = this._absolutizeExpirationTime(response['expires_in']);
this._scheduleAccessTokenRefresh(response['expires_in'], expiresAt, response['refresh_token']);
if (!isEmpty(expiresAt)) {
response = Ember.merge(response, { 'expires_at': expiresAt });
}
resolve(response);
});
}, (xhr) => {
run(null, reject, xhr.responseJSON || xhr.responseText);
});
});
},
My variant:
const data = { 'grant_type': 'password', 'username': identification, 'password': password };
authenticate: function () {
// var username = this.getProperties('username');
// var password = this.getProperties('password');
const {username, password} = this.getProperties('username', 'password');
this.get('session').authenticate('authenticator:oauth2', username, password).catch((reason) => {
this.set('errorMessage', reason.error || reason);
});
}
It was my mistake.

Resources