We have an angular 5 application built on top of MVC authentication. The application is served up from the Home/Index action, and once the application is loaded, the angular routing takes care of pretty much everything. We are doing this primarily because we wanted to use MVC's Authentication processes (we are using Identity Server 4 as our Oath system).
This works well with one exception: logout. When we attempt to logout, the application seems to be immediately reauthorized and reloads instead of returning us to our Identity Server login page.
Originally, we had success in our development environment through this code:
[HttpPost]
public async Task<IActionResult> Logout()
{
foreach (string key in Request.Cookies.Keys)
{
Response.Cookies.Delete(key);
}
await HttpContext.SignOutAsync();
return Ok();
}
But it was a false positive, because all of our applications were running on localhost, so they had access to each other's cookies. Due to this, the Identity Server cookies were cleared along with the cookies for the Angular application.
We attempted to replace that logic with something like this:
public async Task Logout()
{
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
}
However, it did not log out in either environment (however that code works for one of our MVC applications using the same identity server).
To give some background, our logout process for the Angular application comes from a material menu, where we then prompt the user if they really want to logout with a modal, before calling the logout function. Code snippets of this process:
Method called by logout button:
public openDialog(): void {
let dialogRef = this.dialog.open(ModalComponent, {
width: '250px',
data: { text: this.logoutText, ok: this.okText, cancel: this.cancelText }
});
dialogRef.afterClosed().subscribe(result => {
if (result !== undefined) {
switch (result.data) {
case 'ok':
this.logout();
break;
case 'cancel':
break;
default:
break;
}
}
});
}
private logout(): void {
this.sessionService.logOut();
}
Session service:
public logOut(): void {
this.auth.revokeToken();
this.http.post(this.logoutUrl, undefined).subscribe(x => window.location.reload());
}
As mentioned, calling logout this way ends up with a page refresh and the user not really being logged out. There's probably something simple we are missing, but all of the tinkering I've done so far has not led to any success.
EDIT:
Our angular routing is fairly simple (though the pathing prevents us from calling something like home/logout):
{
path: 'parts',
component: PartsComponent,
canActivate: [AuthGuard],
runGuardsAndResolvers: 'always',
data: { title: 'Parts' }
},
{
path: '',
redirectTo: 'parts',
pathMatch: 'full'
},
{
path: '**',
redirectTo: 'parts'
}
Our MVC routes are also fairly straightforward
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
"Sitemap",
"sitemap.xml",
new { controller = "Home", action = "SitemapXml" });
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
I'm not certain how we could easily route to home/logout with the angular routes the way they are. I'm guessing we'd have to add a route for it. We tried that at one point but it never routed correctly. I'm trying to find my notes on what we tried.
After you cleared the local cookies of your application send the user to the end_session_endpoint of your Idsrv. (The code you showed to clear your session should work, if not I'd the config in startup.cs and debug to check if the redirect really removed the cookie in the browser).
E.g. https://demo.identityserver.io/.well-known/openid-configuration
There you see the endpoint. This should remove the session on the Idsrv. Depending on your setup this could kill your sessions on your other applications that uses the same Identity Server instance.
You can read more about this in the docs.
I don't see a problem with the Angular routing interfering with your server side routing. As long as you ofcourse really do a redirect to that page using a regular window.location.replace
And ofcourse like #Win mentioned it would be a good security guideline to revoke refresh_token and reference_tokens if you use those.
I could not answer for Angular; I'm still working on it. However, client web app could ask IDP to revoke access token and refresh token when user signs out at client. For example, in ASP.Net Core -
using System;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace MY_APP.Controllers
{
public class AccountController : Controller
{
[HttpGet]
public async Task<IActionResult> SignOut()
{
var discoveryClient = new DiscoveryClient("IDP_URL");
var metaDataResponse = await discoveryClient.GetAsync();
var revocationClient = new TokenRevocationClient(
metaDataResponse.RevocationEndpoint,
"CLIENT_NAME",
"CLIENT_SECRET");
// revoke the access token
string accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
if (!string.IsNullOrWhiteSpace(accessToken))
{
var revokeAccessTokenResponse = await revocationClient.RevokeAccessTokenAsync(accessToken);
if (revokeAccessTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the access token.",
revokeAccessTokenResponse.Exception);
}
// revoke the refresh token
string refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var revokeRefreshTokenResponse = await revocationClient.RevokeAccessTokenAsync(refreshToken);
if (revokeRefreshTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the refresh token.",
revokeRefreshTokenResponse.Exception);
}
return SignOut(
new AuthenticationProperties { RedirectUri = "CALL_BACK_URL" },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
}
}
Related
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.
I'm implementing the Keycloak authentication service in my Angular 2 project.
I use a service for logging in, logging out etc.
Authenticating a user and logging out seems to work. I'm now trying to protect some routes. I now have a working AuthGuard.
To check if the user is logged in (in the AuthGuard), I have a isAuthenticated() method in the service.
This is the service:
import { Injectable } from '#angular/core';
declare let Keycloak: any;
#Injectable()
export class KeycloakService {
private keycloak = new Keycloak('app/keycloak/keycloak.json');
constructor() {
this.keycloak.init({onload: 'check-sso'});
console.log(this.keycloak);
}
public login() {
this.keycloak.login();
}
public logout() {
this.keycloak.logout();
}
public isAuthenticated() {
return this.keycloak.authenticated;
}
}
Flow: User logs in, user tries to reach protected route, AuthGuard checks if user is logged in via isAuthenticated().
Note: I don't want to authenticate the user for the complete Angular app. Only for some routes.
Problem
After the user logs in, the user is redirected to the Angular app. After this, the isAuthenticated() method returns still false. Here is why:
I logged the Keycloak object to the console. I found something I didn't understand.
Keycloak object after login redirect
Same Keycloak object after login redirect (but expanded)
First the authenticated property is false. After expanding the authenticated property is true.
Question
Is the way I try to maintain my Keycloak object the correct way?
Consulted sources
https://keycloak.gitbooks.io/securing-client-applications-guide/content/v/2.5/topics/oidc/javascript-adapter.html
https://github.com/keycloak/keycloak/tree/master/examples/demo-template/angular2-product-app/src/main/webapp/app
And others
Basing on the community provided Angular2 example in keycloak's github you can spot some differences in interacting with keycloak js adapter.
Mainly the actual check on the authenticated (and possibly userName) is done on the promise returned from init.
static init(): Promise<any> {
let keycloakAuth: any = new Keycloak('keycloak.json');
KeycloakService.auth.loggedIn = false;
return new Promise((resolve, reject) => {
keycloakAuth.init({ onLoad: 'login-required' })
.success(() => {
KeycloakService.auth.loggedIn = true;
KeycloakService.auth.authz = keycloakAuth;
KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/protocol/openid-connect/logout?redirect_uri=/angular2-product/index.html";
resolve();
})
.error(() => {
reject();
});
});
}
Also the official keycloak js adapter's documentation uses promise for the authenticated check
<head>
<script src="keycloak.js"></script>
<script>
var keycloak = Keycloak();
keycloak.init().success(function(authenticated) {
alert(authenticated ? 'authenticated' : 'not authenticated');
}).error(function() {
alert('failed to initialize');
});
</script>
</head>
If you use check-sso as a parameter to init function, the browser will be routed back to the application if the user is not logged in and will remain unauthenticated.You should use login-required instead to fix this problem.
If you don't want to authenticate the user for the complete App, you should detach the logic of creating the adapter, to make things easier if you have more than one secured component. for exemple you can create a HOC.
PS : in the example below, I am using Reactjs, I hope you can find a similar way to do this in angular:
export default (WrappedComponent) => {
return (props) => {
const [isAutenticated, setIsAutenticated] = useState(false);
const [keycloak, setKeycloak] = useState();
const loadConfig = useCallback(() => {
const keycloak = Keycloak("/keycloak.json"); //The configuration of the adapter in JSON format
keycloak.init({ onLoad: "login-required" }).then((authenticated) => {
setKeycloak(keycloak);
setIsAutenticated(authenticated);
});
}, [Keycloak]);
useEffect(() => {
loadConfig();
}, [loadConfig]);
if (keycloak) {
if (isAutenticated) {
return <WrappedComponent {...props} keycloak={keycloak} />;
} else return <AuthError message="Unable to authenticate" />;
}
return <Loader />;
};
};
you can find a useful source here
I am doing LinkedIn authentication with auth0 in a react app. I have set localhost:3000/upload in callback urls in settings, hopping that after users login at localhost:3000/login, they would be redirected to localhost:3000/upload. However, I always get this error: url localhost:3000/login is not in the list of callback urls. Why would auth0 expect to return to the page where you just logged in after logging in. Shouldn't it be some different url. It just does not make sense to me.
Edit:
export default class AuthService {
constructor(clientId, domain) {
// Configure Auth0
const options = {
allowedConnections: ['linkedin'],
auth: {
params: {responseType: 'code'}
}
};
this.lock = new Auth0Lock(clientId, domain, options)
// Add callback for lock `authenticated` event
this.lock.on('authenticated', this._doAuthentication.bind(this))
// binds login functions to keep this context
this.login = this.login.bind(this)
this.loggedIn = this.loggedIn.bind(this)
}
_doAuthentication(authResult){
// Saves the user token
console.log(authResult);
this.setToken(authResult.idToken)
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
console.log('Error loading the Profile', error)
} else {
console.log(profile)
}
})
}
//....
Please ensure two things:
1). In your react app code
responseType: 'code'
2). On the Auth0 dashboard, under Settings -> Allowed Callback URLs put your callback entry (localhost:3000/upload) - which I think you have done but just in case.
Let me know if you are still having problems.
Make sure that there is no special hidden characters or space between the commas between the URLs when you paste it into the Auth0 Setting site. I didn't realise about this util I put every urls into Vim to check and see that there are such above cases
In the call to AuthProvider, make sure to use to same callback url as the one in Auth0 settings:
const uri='http://localhost:3000/upload';
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={uri}>
To cause a redirect to a different URL after a successful authentication, you need to provide the redirectUrl to Lock, like this:
// Configure Auth0
const options = {
allowedConnections: ['linkedin'],
auth: {
responseType: 'code',
redirectUrl: 'http://localhost:3000/upload'
}
};
this.lock = new Auth0Lock(clientId, domain, options)
(Also notice that the responseType option goes under auth, not under auth.params.)
If you do the redirect, you won't reach the event handler you defined in your login page. You will need to either add an event handler in your destination page (and use responseType:token) or handle authentication results in your server code (this is what you will normally be doing if you are requesting a responseType: code).
the reason why you should set the callback Url in auth0 settings, because any one can use your client id and send request to google or linkedin, get the response to anywhere they set. but with this setting only you can access that response.
once your app is authorized to pull the data from linkedin, linkedin will send the data to where you specified. you should create a page to handle the response from Linkedin server. Let's name that page callback.js and this will be an example of response object.
accessToken: "hNuPLKTZHiE9_lnED0JIiiPNjlicRDp"
appState: null
expiresIn: 7200
idToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRXdSVUl5TURVeE4wSkJPRFZEUlRKRU1EVkZNemsxTXpNNU5VTXlNRGt6T0VWQlJqUkZRUSJ9.eyJodHRwOi8vbG9jYWxob3N0OjMwMDAvcm9sZSI6InNpdGVPd25lciIsImdpdmVuX25hbWUiOiJvbWFyIiwiZmFtaWx5X25hbWUiOiJpYm8iLCJuaWNrbmFtZSI6Im9tYXJpYm8xOTgyIiwibmFtZSI6Im9tYXIgaWJvIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250BQUFBQUkvQUFBQUFBQUFBQUEvQUNIaTNyLTEwLTEyVDIyOjU4OjAxLjgzM1oiLCJpc3MiOiJodHRwczovL3BvcnRmb2xpby15aWxtYXouYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTE0MDY0NTA2ODI2OTgwNTA5ODY3IiwiYXVkIjoiUEdVY242RjRRS21PRkJhb1k0UFdCeWpjVzIyT09vNGMiLCJpYXQiOjE1NzA5MjEwODIsImV4cCI6MTU3MDk1NzA4MiwiYXRfaGFzaCI6InN0R1l5SnJaMHNnbVYzSWNLWjlPeFEiLCJub25jZSI6InRrOV95b096enRmVThVVjFVMlVFR3IyMW5ORW5abjk4In0.TYS7mM8N2d7jEHFdWQGTSeAAUaDt4-0SMUG3LrcQ1r3xzY0RMGsUsEszj5xqk1GE0cIlFS10xCOYKsuHSwsFLomC1EbLjntjkledHtfD0MW84cMoXN6a-x-1-bNwl3lMYJ98qklTrNvTvkQJ6DWhei3hJ8rs8dnbNyCfckNVU6ptJU-9ef1DwWfHRomW5LQ6WSDRHZScW697gdgBEMU-Nd2SddyHhQe0kVh6lKdcbnskEAyCJLE07jfM40RQI_8LJouFcpoyImcXSDZlKv90tYfVDq9_TwE3GNaSz5I5snn0457oCgz0vuX0JoCUiaDuTIX7XiyXnozW_DxGMuhk4w"
idTokenPayload: {http://localhost:3000/role: "siteOwner", given_name: "me", family_name: "you", nickname: "nck", name: "nm", …}
refreshToken: null
scope: null
state: "xkEbffzXbdOYPLkXOUkrQeb0Jysbnlfy"
tokenType: "Bearer"
//THIS CODE IS FOR NEXT.JS9
//auth.js
class Auth0 {
constructor() {
this.auth0 = new auth0.WebAuth({
domain: "portfolio-ys.auth0.com",
clientID: "PGUWJQKmOFBaoY4PWByjcW22OOo4c",
redirectUri: "http://localhost:3000/callback",
responseType: "token id_token",
scope: "openid profile"
});
this.handleAuthentication = this.handleAuthentication.bind(this);
}
//there are too many methods are defined here i put only relevant ones
handleAuthentication() {
return new Promise((resolve, reject) => {
this.auth0.parseHash((err, authResult) => {
console.log(authResult);
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
resolve();
} else if (err) {
reject(err);
}
});
});
}
setSession function is where you set the cookies based on response object. I use js-cookie package to set the cookie.
setSession(authResult) {
const expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
);
Cookies.set("user", authResult.idTokenPayload);
Cookies.set("jwt", authResult.idToken);
Cookies.set("expiresAt", expiresAt);
}
}
const auth0Client = new Auth0();
export default auth0Client;
callback.js
import React from "react"
import auth0Client from "./auth0"
import {withRouter} from "next/router"
class Callback extends React.Component{
async componentDidMount(){
await auth0Client.handleAuthentication()
this.props.router.push('/')
}
render() {
return (
<h1>verifying logging data</h1>
)
}
}
export default withRouter(Callback) //this allows us to use router
I had similar issue "callback URL mismatch" and resolved it by running the application over https with a trusted certificate.
Here is a snippet from Auth0 applications settings section about callback URL, which says "Make sure to specify the protocol (https://) otherwisw the callback may fail in some cases."
If you're using the Android(Kotlin) SDK of auth0, I noticed that during runtime, the requested URL is being changed. e.g. app://{your_auth0_domain}/android/{package_name}/callback://{your_auth0_domain}/android/app://{your_auth0_domain}/android//callback
Originally URL was
app://{your_auth0_domain}/android/{package_name}/callback
and SDK is appending "://{your_auth0_domain}/android/app://{your_auth0_domain}/android//callback" this extra part.
Solution: Either put the same URL in auth0 setting dashboard as it showing in your logs
or
WebAuthProvider
.login(account)
.withScheme("app") // instead of complete URL, put only the remaining part from the URL,
.start(this, object : Callback<Credentials, AuthenticationException> {}
I hope it will definitely help android/app developer.
We want to have 2 sets of resources for our AngularJS app (public/private) which uses RequireJS for dependency management. Basically everything on the login page would be public and once logged in, another angularjs app would be loaded (new requirejs config) that would load resources that require authentication to access.
Is there a way to configure requirejs to set an authorization header when loading resources?
It depends on what you mean by "resources" and how your server is configured. But in general - yes, since you are using AngularJS you can use the $httpProvider to inject an interceptor service.
For example, in a service:
var dependencies = ['$rootScope', 'userService'];
var service = function ($rootScope, userService) {
return {
request: function(config) {
var currentUser = userService.getCurrentUser();
var access_token = currentUser ? currentUser.access_token : null;
if(access_token) {
config.headers.authorization = access_token;
}
return config;
},
responseError: function (response) {
if(response.status === 401) {
$rootScope.$broadcast('unauthorized');
}
return response;
}
};
};
module.factory(name, dependencies.concat(service));
Then, after you configure your routes, you can use:
$httpProvider.interceptors.push( 'someService');
You can find some more information on interceptors here: https://docs.angularjs.org/api/ng/service/$http#interceptors
UPDATE
You might be able to use the text plugin to try and receive it, but I don't see the point in protecting client side code. Plus, if you want to use optimization the resources will just come in one file anyway...
config: {
text: {
onXhr: function (xhr, url) {
xhr.setRequestHeader('Authorization','Basic ' + token);
}
}
}
Refer to: custom-xhr-hooks
Another UPDATE
You could also use urlArgs (mainly used for cache invalidation) without using the text plugin:
require.config({
urlArgs: 'token='+token,
...
)}
I am an experienced (55+ years) programmer but a total noob in ember and js. I'm trying to get a simple authentication page working using the ember-cli addons ember-cli-simple-auth, ember-cli-simple-auth-oauth2 and cut-and-paste from the simplelabs tutorial.
I get the following in the console:
DEPRECATION: The LoginControllerMixin is deprecated. Use the session's authenticate method directly instead.
and:
DEPRECATION: The AuthenticationControllerMixin is deprecated. Use the session's authenticate method directly instead.
The solution may be trivial, but I have been chasing it for hours and get deep into javascript before reaching a dead-end. The code that is causing these errors is:
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:oauth2-password-grant'
});
which invokes the ApplicationControllerMixin somewhere in the bower code.
Before I "re-invent the wheel" by translating some old html/ruby/pascal code into js, can anyone help me "Use the session's authenticate method directly instead."?
Thanks.
I feel you're pain. I spent weeks trying to sort this out. A big part of the problem is that so much has changed in the past couple of years and there are a lot of code examples out there that are outdated or don't work together. It's really difficult to put the various pieces together coherently, and figure out what one does NOT need to do.
That said, please keep in mind that i'm a n00b as well. What i've done seems to work ok but i've no idea whether there's a much better way.
Also, what you're trying to do may not be the same as what i've done. My app authenticates against google (and twitter, fb, etc.) using Simple-Auth-Torii and then exchanges the returned Authenication Code for an Authentication Token. That last part happens on the server. So, after the session authenticates, i then pass the auth code to the server and get back the auth code.
// routes/login.js
import Ember from "ember";
import ENV from "../config/environment";
export default Ember.Route.extend({
setupController: function(controller, model) {
controller.set("errorMessage", null);
},
actions: {
googleLogin: function() {
var _this = this;
// Using "session's authenticate method directly" right here
this.get("session").authenticate("simple-auth-authenticator:torii", "google-oauth2")
.then(function() {
// We're now authenticated. The session should now contain authorization
// code from provider. We now need to exchange that for an auth token.
var secureData = _this.get("session.content.secure");
// call server to initiate token exchange from provider
var exchangeData = {
authorizationCode: secureData.authorizationCode,
redirectUri : secureData.redirectUri,
provider : 'google'
};
// sends ajax request to server, which will in turn call provider
// with authentication code and receive auth token
_this.tokenService.fetch(exchangeData).then(function(response) {
if (response.success) {
_this.set("session.content.secure.access_token", response.data.token);
_this.set("session.content.secure.userData", response.data.user);
// take user somewhere ...
_this.transitionTo("data_sets");
}
else {
// set an error message, log the response to console, whatever, but
// we need to invalidate session because as far as simple-auth
// is concerned we're already authenticated. The following logs the user out.
_this.get("session").invalidate();
}
}, function(error) {
console.log("tokenService.fetch error", error);
_this.get("session").invalidate();
});
}, function(error) {
console.log("simple-auth-authenticator:torii error", error);
_this.get("session").invalidate();
});
},
twitterLogin: function() {
// etc.
}
}
});
Logging a user out also uses the session directly.
{{!templates/application.hbs}}
<ul class="nav navbar-nav navbar-right">
{{#if session.isAuthenticated}}
<li><button {{ action 'invalidateSession' }} class="btn btn-sm">Logout</button></li>
{{/if}}
...
</ul>
// routes/application.js
import Ember from "ember";
import ENV from "../config/environment";
import ApplicationRouteMixin from "simple-auth/mixins/application-route-mixin";
export default Ember.Route.extend(ApplicationRouteMixin, {
actions: {
// action is globally available because in application route
invalidateSession: function() {
// the most basic logout
this.get("session").invalidate();
return;
// If you need to invalidate also on the server do something like:
//
// var _this = this;
// return new Ember.RSVP.Promise(function(resolve, reject) {
// var params = {
// url : ENV.logoutEndpoint,
// type : "POST",
// dataType : "json"
// };
// Ember.$.ajax(params).then(function(response) {
// console.log('session invalidated!');
// console.dir(response);
// _this.get("session").invalidate();
// });
// });
}
}
});
I've met the same deprecation problem. I thinks this snippet for a lovely login controller will do, it's a bit more what you asked, but I hope it is still understandable. I use it with devise, it's almost the same except I use it this way: authenticate('simple-auth-authenticator:devise', credentials)
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
authenticate: function() {
// identification and password are the names of the input fields in the template
var credentials = this.getProperties('identification', 'password');
if (!credentials.identification || !credentials.password) {
return false;
}
this.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', credentials).then(function() {
// authentication was successful
}, function(errorMessage) {
// authentication failed
});
}
}
});