1
votes

We've been using OpenIdDict with the password flow.

But: we want to switch to implicit flow (a popup window from the openiddict auth server). I can find a sample for an ASP.NET application, but we want to use this in: 1/ an angular 1.0 website 2/ an Ionic 2.0 app

Can somebody give some hints how to this in? (the Aurelia example comes close, but we can't manage to get it working in Ionic or Angular). Which libraries can we use, what calls should we make?

Thanks a lot,

Frank
1

1 Answers

3
votes

I only used implicit flow with OpenIddict from angular2 apps with typescript but I strongly advise you to use oidc-client. The same methodology for all SPA apply anyway.

You can make an angular service that wraps this library, containing the UserManager and with a few methods shorthands like signIn, signInCallback...

@Injectable()
export class OAuthService {
    private client: UserManager;
    private user: Promise<User | null>;

    constructor(private ngZone: NgZone, @Inject(OAUTH_CONFIG_TOKEN) options: IOAuthConfig) {
        Log.logger = console;

        Log.level = Log.INFO;
        this.client = new UserManager(<UserManagerSettings>{
            authority: options.authority, // the url of your openiddict service
            client_id: options.client_id, // your openiddict application id
            redirect_uri: options.redirect_uri, // the url of your app after login
            post_logout_redirect_uri: options.post_logout_redirect_uri, // the url of your app after logout
            scope: options.scope, // your openid scopes like 'openid email profile roles'
            response_type: 'id_token token', //id + access token
            filterProtocolClaims: true,
            loadUserInfo: true,
        });
        this.client.events.addUserLoaded(user => {
            this.user = Promise.resolve(user);
            Log.logger.info('OAuthService: User loaded');
        });
        this.client.events.addUserSignedOut(() => Log.logger.info('OAuthService: User signed out'));
        this.client.events.addUserUnloaded(() => Log.logger.info('OAuthService: User unloaded'));
        this.client.events.addSilentRenewError(err => Log.logger.error(err));
        this.user = this.ngZone.runOutsideAngular(() => {
            return this.client.getUser().then((user) => {
                if (user == null) {
                    return null;
                }
                return user;
            });
        });
    }

    public get events(): UserManagerEvents {
        return this.client.events;
    }


    public signin(): Promise<any> {
        return this.ngZone.runOutsideAngular(() => {
            return this.client.signinRedirect()
                .catch(err => Log.logger.error(err));
        });
    }

    public signinCallback(): Promise<User|null> {
        this.user = this.ngZone.runOutsideAngular(() => {
            return this.client.signinRedirectCallback()
                .then(user => {
                    if (user == null) {
                        return null;
                    }
                    return user;
                })
                .catch(err => Log.logger.error(err));
        });
        return this.user;
    }

    public signout(): Promise<any> {
        return this.ngZone.runOutsideAngular(() => {
            return this.client.signoutRedirect()
                .catch(err => Log.logger.error(err));
        });
    }

    public signoutCallback(): Promise<any> {
        return this.client.signoutRedirectCallback()
            .catch(err => Log.logger.error(err));
    }

    public authorizationHeader(): Promise<string> {
        return this.ngZone.runOutsideAngular(() => {
            return this.user
                .then(user => user == null ? '' : user.token_type + ' ' + user.access_token);
        });
    }

    public getUser(): Promise<User|null> {
        return this.ngZone.runOutsideAngular(() => {
            return this.user
                .catch(err => Log.logger.error(err));
        });
    }

    public isLoggedIn(debugToken = ''): Promise<boolean> {
        return this.ngZone.runOutsideAngular(() => {
            return this.user.then(user => {
                if (user != null) {
                    return !user.expired;
                }
                return false;
            });
        });
    }

}

I had compatibility issues between angular2 and oidc-client promises so I had to use NgZone as you can see.

Inject this service everywhere you need it (profile field of User contains your claims like the role, unique_name and so on).

Make two components like LoginComponent and LogoutComponent that simply call signinCallback and signoutCallback and redirect to somewhere in your app like your homepage.

@Component({
    selector: 'app-logout',
    templateUrl: './logout.template.html'
})
export class LogoutComponent implements OnInit {
    constructor(private router: Router, private oauthService: OAuthService) { }

    ngOnInit() {
        this.oauthService.signoutCallback().then(() => this.router.navigate(['']));
    }
}

Those two components routes must match the given UserManager configuration in OAuthService constructor.

{
    path: 'login',
    component: LoginComponent
},
{
    path: 'logout',
    component: LogoutComponent
},

For instance if redirect_uri is http://localhost:5000/login and post_logout_redirect_uri is http://localhost:5000/login

You'll probably need to subscribe some event in order to refresh some properties when the user just login.

public ngOnInit(): void {
    this.onUserChange();
    this.oauthService.events.addUserLoaded(() => this.onUserChange());
    this.oauthService.events.addUserUnloaded(() => this.onUserChange());
}

private onUserChange(): void {
    this.oauthService.isLoggedIn().then(x => this.isLoggedIn = x);
}

After this you're pretty much done, silent login is not yet supported in OpenIddict and I didn't found a workaround yet.

Again this is all Typescript/Angular2 but it should be easily adaptable to JS/Angular