The question must be very typical, but I can't really find a good comparison.
I'm new to Ionic & mobile dev.
We have a REST API (Spring Boot).
API is currently used by AngularJS 1.5 front-end only.
AngularJS app is authenticated based on the standard session-based authentication.
What should I use to authenticate an ionic 3 app?
As I understand, have 2 options:
Use the same auth as for Angular front-end.
implement oauth2 on the back-end and use the token for the ionic app.
As for now, I understand that implementing oauth2 at back-end is a way to go because with the option #1 I should store the username & password in the local storage (ionic app), which is not safe. Otherwise, if I don't do that - the user will have to authenticate each time the app was launched. Am I right?
So, that leaves me with option #2 - store oauth2 token on the device?
Good to go with #2. Here is how i manage token.
I use ionic storage to store token and a provider config.ts which hold the token during run time.
config.ts
import { Injectable } from '#angular/core';
#Injectable()
export class TokenProvider {
public token: any;
public user: any = {};
constructor( ) { }
setAuthData (data) {
this.token = data.token;
this.user = data
}
dropAuthData () {
this.token = null;
this.user = null;
}
}
auth.ts
import { TokenProvider} from '../../providers/config';
constructor(public tokenProvider: TokenProvider) { }
login() {
this.api.authUser(this.login).subscribe(data => {
this.shared.Loader.hide();
this.shared.LS.set('user', data);
this.tokenProvider.setAuthData(data);
this.navCtrl.setRoot(TabsPage);
}, err => {
console.log(err);
this.submitted = false;
this.shared.Loader.hide();
this.shared.Toast.show('Invalid Username or Password');
this.login.password = null;
});
}
and i do a check when app launch.
app.component.ts (in constructor)
shared.LS.get('user').then((data: any) => {
if (!data) {
this.rootPage = AuthPage;
} else {
tokenProvider.setAuthData(data);
this.rootPage = TabsPage;
}
});
api.provider.ts
updateUser(data): Observable < any > {
let headers = new Headers({
'Content-Type': 'application/json',
'X-AUTH-TOKEN': (this.tokenProvider.token)
});
return this.http.post(`${baseUrl}/updateUser`, JSON.stringify(data), {
headers: headers
})
.map((response: Response) => {
return response.json();
})
.catch(this.handleError);
}
And last logout.ts
logOut(): void {
this.shared.Alert.confirm('Do you want to logout?').then((data) => {
this.shared.LS.remove('user').then(() => {
this.tokenProvider.dropAuthData();
this.app.getRootNav().setRoot(AuthPage);
}, () => {
this.shared.Toast.show('Oops! something went wrong.');
});
}, err => {
console.log(err);
})
}
The final solution i've made:
ionic app:
implemented a jwt token storage similar to Swapnil Patwa answer.
Spring back-end:
Tried to use their original ouath2 package, but found out that as always with spring/java, configs are too time-consuming => made a simple filter which is checking for the manually generated & assigned jwt token.
Related
I have employed the Login with Google functionality in my React app. I am getting the jwt but there is no access token included in the jwt which I need for sending it to the backend (Laravel). On the backend I use Socialite and I want to get the user back with the access token. Right now I am verifying the user with jwt which is not working.
React Code.
const handleGoogleCallbackResponse = (response) => {
signinWithGoogle(response.credential)
}
const signinWithGoogle = async (jwt) => {
try {
const res = await axios.post("/api/users/loginwithgoogle", {jwt: jwt})
console.log("Google data from backend: ", res.data);
} catch (error) {
console.log("Error at signinWithGoogle : ", error);
}
}
useEffect(() => {
/* global google */
google.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
callback: handleGoogleCallbackResponse
})
google.accounts.id.renderButton(document.getElementById("google-btn"), {theme: "outline", size: "large"})
}, [])
Backend:
$user = Socialite::driver('google')->stateless()->userFromToken($request->jwt);
I stitched together a lot of tutorials and documentation in order to get an access token with MSALin my JavaScript code. Here are the results of my research.
npm install #azure/msal
import the necessary class from #azure/msal
import {
UserAgentApplication,
AuthenticationParameters,
Configuration,
} from "#azure/msal";
Make the msal object
const config: Configuration = {
auth: {
clientId: <client id - your app's client id>,
authority: `https://login.microsoftonline.com/<tenantid>`,
redirectUri: <the redirect Uri>,
},
};
const params: AuthenticationParameters = {
authority: `https://login.microsoftonline.com/${Tenantid}`,
scopes: [`${AppIDUri}/user_impersonation`], <-- the API that you're trying to call
};
const myMSAL = new UserAgentApplication(config);
Get access token
try {
const login = await myMSAL.acquireTokenSilent(params);
return login.accessToken;
} catch (error) {
await myMSAL.loginPopup(params);
const login = await myMSAL.acquireTokenSilent(params);
return login.accessToken;
}
References:
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens
Azure/Msal authentication inside PowerApp Component Framework returns AADSTS50177 error
I followed the example here https://stormpath.com/blog/the-ultimate-guide-to-mobile-api-security
and here to acquire an access token
https://support.stormpath.com/hc/en-us/articles/225610107-How-to-Use-Stormpath-for-Token-Management
"use strict";
import { ApiKey } from 'stormpath';
import { Client } from 'stormpath';
let apiKey = new ApiKey(process.env.STORMPATH_API_KEY_ID,
process.env.STORMPATH_API_KEY_SECRET);
let spClient = new Client({apiKey: apiKey });
spClient.getApplication(process.env.STORMPATH_APPLICATION_HREF,
function(err, app) {
var authenticator = new OAuthAuthenticator(app);
authenticator.authenticate({
body: {
grant_type: 'password',
username: username,
password : password
}
}, function (err, result) {
if (!err) console.log(err);
res.json(result.accessTokenResponse);
});
});
I was able to acquire a access_token. I use this token to hit my api with Header Authorization Bearer {access_token}
However, when i put in the middleware stormpath.apiAuthenticationRequired, i keep getting this warning and my api is returned with 401
(node:57157) DeprecationWarning: JwtAuthenticator is deprecated, please use StormpathAccessTokenAuthenticator instead.
I am implementing IdentityServer4 with variety of clients, One of the clients is a Javascript application, I have implemented the implicit flow for authentication and everything is working fine.
On my Javascript application , I have a button to login, once I click on the button I am redirected to IdentityServer and after successful login I am redirected back to my application along with my access token.
Now what I want to do is, move the login to the client side so that each application can have its own login UI (with its own theme).
app.js
function log() {
document.getElementById('results').innerText = "";
Array.prototype.forEach.call(arguments, function (msg) {
if (msg instanceof Error) {
msg = "Error:" + msg.message;
}
else if (typeof msg !== 'string') {
msg = JSON.stringify(msg, null, 2);
}
document.getElementById('results').innerHTML += msg + "\r\n";
});
}
document.getElementById("login").addEventListener('click', login, false);
document.getElementById('api').addEventListener('click', api, false);
document.getElementById("logout").addEventListener("click", logout, false);
//configure client
var config = {
authority: "http://localhost:5000",
client_id: "js",
redirect_uri: "http://localhost:5004/callback.html",
response_type: "id_token token",
scope: "openid profile api1 role",
post_logout_redirect_uri: "http://localhost:5004/index.html"
};
//init user manager
var mgr = new Oidc.UserManager(config);
//check if user is logged in already
mgr.getUser().then(function (user) {
if (user) {
log("User logged in", user.profile);
} else {
log("User is not logged in.");
}
});
function login() {
mgr.signinRedirect();
}
function api() {
mgr.getUser().then(function (user) {
var url = "http://localhost:5001/identity/getfree";
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function () {
log(xhr.status, JSON.parse(xhr.responseText));
};
xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
xhr.send();
});
}
function logout() {
mgr.signoutRedirect();
}
IdentityServer StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
var connectionString = "Server=localhost;port=3306;database=netcore;uid=root;Password=Liverpool1";
services.AddApplicationInsightsTelemetry(Configuration);
services.AddDbContext<ApplicationDbContext>(options => options.UseMySQL(connectionString));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryScopes(Config.GetScopes())
.AddInMemoryClients(Config.GetClients())
// .AddConfigurationStore(builder => builder.UseMySQL(connectionString))
//.AddOperationalStore(builder => builder.UseMySQL(connectionString))
.AddAspNetIdentity<ApplicationUser>();
}
This is not possible and breaks the whole point of implicit flow and all the other federated sign on flows. The whole point of implicit flow is that you do not pass user credentials through the client but rather it goes to the identity provider.
You have two options:
Finding out a way to serve up different logins per "tenant" in
ASP.NET Core.
Use Resource Owner flow and pass the user credentials
through the client.
Option 1 is probably the best but requires more work, option 2 is a cop out and using RO flow is an anti-pattern.
I have a bunch of REST web services powered by Spring Boot. These web services are protected by a basic http authentication.
I try to create an Angular2 front end, hosted on another server. I want the front end to interact with the spring boot back end, using credentials provided by the user in the front end.
Right now, the user can log in through the angular2 front end, but then, even with user authenticate, each call to the back end requires authentication, which is normal I guess.
So my question is: how can I tell angular2 to automatically add the authorization header with the user credentials only when the user is logged?
Thanks for your help
I would have a look at what Ben Nadel has done http://www.bennadel.com/blog/3047-creating-specialized-http-clients-in-angular-2-beta-8.htm (or the typescript version by Sam Storie https://blog.sstorie.com/adapting-ben-nadels-apigateway-to-pure-typescript/). I have not had a chance to use this in an app yet, but it seems very promising.
Also, if you have not already, I would take a look at Dave Syer's Spring Boot and AngularJS tutorials (https://spring.io/guides/tutorials/spring-security-and-angular-js/). They are written for Angular 1, but they are very useful.
Below is my "solution". It works, but I am not thrilled with it:
main.ts:
import { CsrfBaseRequestOptions } from './app/shared';
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
...
provide(RequestOptions, { useClass: CsrfBaseRequestOptions })
]);
XhrBaseRequestOptions:
#Injectable()
export class XhrBaseRequestOptions extends BaseRequestOptions {
constructor() {
super();
this.headers.append('X-Requested-With', 'XMLHttpRequest');
}
}
CsrfBaseRequestOptions:
import { Injectable } from '#angular/core';
import { XhrBaseRequestOptions } from './xhr-base-request-options';
#Injectable()
export class CsrfBaseRequestOptions extends XhrBaseRequestOptions {
constructor() {
super();
let csrfToken = this.getCsrfToken('X-CSRF-TOKEN');
if (csrfToken) {
this.headers.append('X-CSRF-TOKEN', csrfToken);
}
}
getCsrfToken(tokenName:string):string {
let tokenNameEQ = tokenName + '=';
let ck = document.cookie;
let ca = ck.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(tokenNameEQ) === 0) return c.substring(tokenNameEQ.length, c.length);
}
return null;
}
}
We have a similar situation and used a custom service to handle the auth-header.
The service has a Http injected and if a JWT is available in localstorage it adds the auth header to the get/post request
let authHeader = new Headers();
authHeader.append('Authorization', 'Bearer ' + jwt);
And then add it to the request via
this.http.get(url, { headers: authHeader });
Hope that helps...