We are developing a component that handles OpenID Connect's implicit flow.
In step 5 of the flow, the "Authorization Server sends the End-User back to the Client with an ID Token and, if requested, an Access Token." We would like our component to handle that request, which will be to ~/openid-login.
How do we configure Aurelia to have it route to a function in our component?
export class OpenId {
// how do we route ~/openid-login to this?
public handleRequest() {
}
}
Note: Here is the work in progress.
Using a navStrategy within your routeConfig will allow you to do what ever you like before navigating to a page. See below:
import { autoinject } from 'aurelia-framework';
import { RouterConfiguration, Router, NavigationInstruction } from 'aurelia-router';
#autoinject
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
let openIdNavStrat = (instruction: NavigationInstruction) => {
console.log('Do whatever we would like to do.');
// then redirect to where ever you would like.
instruction.config.moduleId = 'login';
}
config.map([
{ route: ['', 'login'], moduleId: 'login' },
{ route: 'openid-login', navigationStrategy: openIdNavStrat },
]);
this.router = router;
}
}
There is documentation on Navigation Strategies here: http://aurelia.io/hub.html#/doc/article/aurelia/router/latest/router-configuration/3
Related
In my Angular app, if I load the home page / and then navigate to, say, /products, it works fine (it's a lazy-loaded module). But if now I reload the page, the browser makes a GET /products call to the server, which results in a 404.
The solution is to send index.html and the Angular app is back on rails. So in Express I do app.all("*", (req,res) => { res.sendFile("index.html") }) and it works.
How to do the same thing in Nest?
There is a #All decorator, but each controller in a given component handles a subroute, for instance #Controller("cats") will match /cats routes, so if I add #All in this controller, it will match only /cats/*, not *.
Must I really create a whole separate module with a controller, just for this? That's what I did
#Controller() // Matches "/"
export class GenericController {
#All() // Matches "*" on all methods GET, POST...
genericFunction(){
console.log("Generic route reached")
}
}
And in my main module :
#Module({
imports: [
ItemsModule, // Other routes like /items
GenericModule, // Generic "*" route last
],
})
It works, but it seems overkill. Is this the way to go or is there a simpler trick?
So, will be best to use global-scoped exception filter.
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalFilters(new NotFoundExceptionFilter());
await app.listen(3000);
}
bootstrap();
NotFoundExceptionFilter:
import { ExceptionFilter, Catch, NotFoundException } from '#nestjs/common';
import { HttpException } from '#nestjs/common';
#Catch(NotFoundException)
export class NotFoundExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
// here return `index.html`
}
}
Maybe it will not work, will test later
You don't need to create a separated GenericModule. However, GenericController is fully valid and you approach is definitely a good one. The question is rather what would you like to achieve using this generic route. If handling "Route not found" error is your requirement, a better choice is an exception filter.
An alternative answer in 2022.
I've solved this by specifying the routes in the order I want them evaluated. In my instance I am using a fallback route to catch all requests, but if I need custom processing I want to create a route which superceeds the fallback route.
However, in defining a catchall route /api/:resource in the AppController, I found the fallback route would overwrite all other routes.
My solution to this is to define the fallback route in it's own module and ensure that it is appended to the list of modules. This way it is created last and will only catch what falls through.
#router.ts
import {RouterModule} from '#nestjs/core/router';
import {ContentBlockModule} from './content_block/content_block.module';
import {FallbackModule} from './fallback/fallback.module';
const APIRoutesWithFallbackRoute = RouterModule.register([
{
// This lets me avoid prepending my routes with /api prefixes
path: 'api',
// Overload the /api/content_blocks route and foward it to the custom module
children: [
{
path: 'content_blocks',
module: ContentBlockModule,
},
],
},
{ //Fallback Route catches any post to /api/:resource
path: 'api',
module: FallbackModule,
},
]);
#app.module
App module imports the fallback module. Important Ensure FallbackModule is the last module to be declaired or it will overwrite routes that are included after it.
import {Module} from '#nestjs/common';
import {AppService} from './app.service';
import {APIRoutesWithFallbackRoute} from './APIRoutesWithFallbackRoute';
import {ContentBlockModule} from './content_block/content_block.module';
import {FallbackModule} from './fallback/fallback.module';
// APIRoutes include first, Fallback Routes prepended.
#Module({
imports: [APIRoutesWithFallbackRoute, ContentBlockModule, FallbackModule],
controllers: [],
providers: [AppService],
})
export class AppModule {}
FallbackController
import {Controller, Post, Req, Res} from '#nestjs/common';
import {defaultHandler} from 'ra-data-simple-prisma';
import {FallbackService} from './fallback.service';
#Controller()
export class FallbackController {
constructor(private readonly prisma: FallbackService) {}
#Post(':resource')
fallback(#Req() req, #Res() res) {
// return this.appService.getData();
console.log('executing from the default fallback route');
return defaultHandler(req, res, this.prisma);
}
}
ContentBlockController
The content block controller, included here for completeness.
#Controller()
export class ContentBlockController {
constructor(
private readonly contentBlockService: ContentBlockService,
private readonly prisma: PrismaService,
) {}
#Post()
async create(
#Body() contentBlock: content_blocks,
#Req() req: Request,
#Res() res: Response,
): Promise<void> {
console.log('executing from the resource specific route');
// lean on my service to do heavy business logic
const [model, values] = await this.contentBlockService.createContentBlock(
contentBlock,
);
// inject custom logic...
const alteredRequest: CreateRequest = {
...req,
body: {
...req.body,
params: {
data: values,
},
},
};
return createHandler(alteredRequest, res, model);
}
}
Using this system I am able to define a single route to handle 90% of the routes necessary to expose my Prisma models to my private API. And if I need custom logic I have full control.
I have a view that can be accessed by a direct link from an email.
Ex.
http://myServer:7747/#/pics/ClientId/YYYY-MM-DD
So this is set up using a route:
{ route: ['/pics', '/pics/:clientId/:sessionDate', 'pics'],
name: 'pics', moduleId: './views/pics', nav: false, title: 'Pictures',
auth: true, activationStrategy: activationStrategy.invokeLifecycle
},
So if a client clicks on this link and is not logged in, I want the view to redirect to a login screen (I am using aurelia-authentication plugin) and then when it succeeds, I want it to return to this page using the same urlParams.
I have the redirect to the login page working, but getting back to this view is proving difficult. If I just try to use history.back() the problem is that the authentication plugin has pushed another navigationInstruction (loginRedirect) onto the history before I can do anything. If I just try to hard-code a 'go back twice' navigation I run into a problem when a user simply tries to log in fresh from the main page and there is no history.
Seems like this should be easier than it is, what am I doing wrong?
I haven't used the aurelia-authentication plugin, but I can help with a basic technique you can use that makes this very easy. In your main.js file, set the root of your app to a "login" component. Within the login component, when the user has successfully authenticated, set the root of your app to a "shell" component (or any component you choose) that has a router view and configure the router in its view-model. Once this happens, the router will take the user to the proper component based on the url. If the user logs out, just set the app root back to the "login" component.
Here's some cursory code to attempt to convey the idea. I assume you're using the SpoonX plugin, but that's not really necessary. Just as long as you reset the root of your app when the user authenticates, it will work.
In main.js
.....
aurelia.start().then(() => aurelia.setRoot('login'));
.....
In login.js
import {AuthService} from 'aurelia-authentication';
import {Aurelia, inject} from 'aurelia-framework';
#inject(AuthService, Aurelia)
export class Login {
constructor(authService, aurelia) {
this.authService = authService;
this.aurelia = aurelia;
}
login(credentialsObject) {
return this.authService.login(credentialsObject)
.then(() => {
this.authenticated = this.authService.authenticated;
if (this.authenticated) {
this.aurelia.setRoot('shell');
}
});
}
.....
}
In shell.html
.....
<router-view></router-view>
.....
In shell.js
.....
configureRouter(config, router) {
this.router = router;
config.map(YOUR ROUTES HERE);
}
.....
I got this to work by replacing the plugin's authenticateStep with my own:
import { inject } from 'aurelia-dependency-injection';
import { Redirect } from 'aurelia-router';
import { AuthService } from "aurelia-authentication";
import { StateStore } from "./StateStore";
#inject(AuthService, StateStore)
export class SaveNavStep {
authService: AuthService;
commonState: StateStore;
constructor(authService: AuthService, commonState: StateStore) {
this.authService = authService;
this.commonState = commonState;
}
run(routingContext, next) {
const isLoggedIn = this.authService.authenticated;
const loginRoute = this.authService.config.loginRoute;
if (routingContext.getAllInstructions().some(route => route.config.auth === true)) {
if (!isLoggedIn) {
this.commonState.postLoginNavInstr = routingContext;
return next.cancel(new Redirect(loginRoute));
}
} else if (isLoggedIn && routingContext.getAllInstructions().some(route => route.fragment === loginRoute)) {
return next.cancel(new Redirect(this.authService.config.loginRedirect));
}
return next();
}
}
The only difference between mine and the stock one is that I inject a 'StateStore' object where I save the NavigationInstruction that requires authentication.
Then in my login viewModel, I inject this same StateStore (singleton) object and do something like this to log in:
login() {
var redirectUri = '#/defaultRedirectUri';
if (this.commonState.postLoginNavInstr) {
redirectUri = this.routing.router.generate(this.commonState.postLoginNavInstr.config.name,
this.commonState.postLoginNavInstr.params,
{ replace: true });
}
var credentials = {
username: this.userName,
password: this.password,
grant_type: "password"
};
this.routing.auth.login(credentials,
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
redirectUri
).catch(e => {
this.dialogService.open({
viewModel: InfoDialog,
model: ExceptionHelpers.exceptionToString(e)
});
});
};
Hope this helps someone!
I'm building my first app using the release version of Angular 2 (I'm on 2.1.0 currently). I have set up a Route Guard, and I am using it in my routing.ts to secure one of the routes with authentication. When clicked, if not logged in, it redirects the user to the login route. There they can login, and if authenticated, it sets a localStorage token. Here's where I have a problem. I want to then redirect the user to the route they clicked on initially before they were redirected to the login, but I can't figure out how to get the clicked route once they hit the Guard canActivate method, or on the login. This seems like a fairly common usage scenario, but I can't find any examples of doing this.
Ok this is my stripped out example which should illustrate the point:
#Injectable()
export class AuthGuardService implements CanActivate
{
toUrl;
constructor(public authenticationService: AuthenticationService,
public router: Router)
{
}
canActivate(route, state): boolean
{
this.toUrl = state.url; //This is the url where its going
if (this.authenticationService.isLoggedIn()) return true;
this.router.navigate(['/login', {redirect: this.toUrl}]);
}
}
and in the login component use the ngOnInit to check for any redirect ulrs:
export class LoginComponent
{
redirect;
constructor(
private authenticationService: AuthenticationService,
private route: ActivatedRoute,
private router: Router)
{
}
ngOnInit()
{
this.redirect = this.route.snapshot.params['redirect'];
}
logIn(): void
{
this.authenticationService
.login(this.searchParams)
.subscribe(
() =>
{
this.logInSuccess();
},
error =>
{
this.logInFail(error)
})
}
logInSuccess(): void
{
if (this.redirect)
{
this.router.navigateByUrl(this.redirect);
}
else
{
this.router.navigate([''])
}
}
}
i am currently implementing an angular2 example application with spring boot as backend. I am having some problems with the frontend auth guard mechanism and observables.
I am trying to achieve:
when someone enters a protected route the auth guard should check if a user
is already set in the auth service variable
if it is not set then a http request should be issued to check if a session is available
the service method should return a true/false value (asynchronously because of the possible http request)
if service returns false the auth guard should redirect to login page
auth guard should return true/false so the route can either be activated or not
My code currently looks like this (i am using RC5 btw.):
Auth Guard
import {Injectable} from "#angular/core";
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from "#angular/router";
import {Observable, Subject} from "rxjs/Rx";
import {AuthService} from "./auth.service";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
var authenticated = this.authService.isAuthenticated();
var subject = new Subject<boolean>();
authenticated.subscribe(
(res) => {
console.log("onNext guard: "+res);
if(!res && state.url !== '/signin') {
console.log("redirecting to signin")
this.router.navigate(['/signin']);
}
subject.next(res);
});
return subject.asObservable();
}
}
Auth Service
import {Injectable} from "#angular/core";
import {User} from "./user.interface";
import {Router} from "#angular/router";
import {Http, Response, Headers} from "#angular/http";
import {environment} from "../environments/environment";
import {Observable, Observer, Subject} from "rxjs/Rx";
#Injectable()
export class AuthService {
private authenticatedUser : User;
constructor(private router: Router, private http: Http) {}
signupUser(user: User) {
}
logout() {
//do logout stuff
this.router.navigate(['/signin']);
}
isAuthenticated() : Observable<boolean> {
var subject = new Subject<boolean>();
if (this.authenticatedUser) {
subject.next(true);
} else {
this.http.get(environment.baseUrl + '/user')
.map((res : Response) => res.json())
.subscribe(res => {
console.log("next: returning true");
this.authenticatedUser = User.ofJson(res);
subject.next(true);
}, (res) => {
console.log("next: returning false");
subject.next(false);
});
}
return subject.asObservable();
}
}
The problem is: the guard never allows the router component to activate, even though when i am logged in.
Thanks for the help!
Change
return subject.asObservable();
to
return subject.asObservable().first();
The router waits for the observable to complete. first() makes it complete after the first event.
I am using code like this to extend RouterOutlet and create app wide authentication and route protection
import {Directive, Attribute, ViewContainerRef, DynamicComponentLoader} from '#angular/core';
import {Router, ComponentInstruction} from '#angular/router';
import {Router} from '#angular/router';
import {RouterOutletMap} from '#angular/router/src/router_outlet_map';
import {RouterOutlet} from '#angular/router/src/directives/router_outlet';
import {Authentication} from '../common/authentication.service';
#Directive({
selector: 'router-outlet'
})
export class LoggedInRouterOutlet extends RouterOutlet {
publicRoutes:any;
isAuthenticated:boolean;
//private router: any;
constructor(public _elementRef: ElementRef, public _loader: DynamicComponentLoader,
public _parentRouter: Router, #Attribute('name') nameAttr: string, public authService:Authentication) {
super(_elementRef, _loader, _parentRouter, nameAttr);
this.isAuthenticated = authService.isLoggedIn();
//this.router = _parentRouter;
/**
* DEFINE PUBLIC ROUTES
*
* The Boolean following each route below denotes whether the route requires authentication to view.
*
* Format: key/value pair
* - key is the /route url "/login", "/signup", etc
* - value is a boolean true/false
* `true` means it's a publicly available route. No authentication required
* `false` means it's a protected route which is hidden until user is authenticated
*
*/
this.publicRoutes = {
'login': true,
'signup': true,
'404': true
};
} // end constructor
routeIsActive(routePath:string) {
return this.router.url == routePath;
}
activate(instruction: ComponentInstruction) {
// let url = instruction.urlPath;
let url = this.router.url;
// If the url doesn't match publicRoutes and they are not authenticated...
if (!this.publicRoutes[url] && !this.isAuthenticated) {
// todo: redirect to Login, may be there a better way?
this.router.navigateByUrl('/login');
}
return super.activate(instruction);
}
}
Problem is that ComponentInstruction does not exist in the new v3.0.0-alpha8 router, and the super method signature has changed. How do I update this to work in the new router? I cannot find any documentation explaining the changes.
ComponentInstruction has been deprecated. In the current RC4 version of Angular2, this class has been listed under reouter-deprecated. With RC5 coming in, this package would be dropped.
RouterOutlet has changed a lot over time and to make your class LoggedInRouterOultet work, you have to use CanActivate interface.
You can do something like this:
Have an injectable service like LoggedInActivator shown here:
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { LogInService } from './login.service';
#Injectable()
export class LoggedInActivator implements CanActivate {
constructor(private loginService: LogInService) {}
canActivate() {
return this.loginService.isLoggedIn();
}
}
Add canActivate and map it to LoggedInActivator on component while defining route:
{ path: 'home', component: HomeComponent, canActivate: [LoggedInActivator] }
I hope this helps!
because in new router, it uses CanActivate