I'm setting up IBM Watson Speech-to-Text. This requires an access token, documented here:
"Tokens have a time to live (TTL) of one hour, after which you can no longer use them to establish a connection with the service. Existing connections already established with the token are unaffected by the timeout. An attempt to pass an expired or invalid token elicits an HTTP 401 Unauthorized status code from DataPower. Your application code needs to be prepared to refresh the token in response to this return code."
I don't see on the documentation page an example of application code to refresh the token.
Should my code generate a new token for every user who downloads the JavaScript app? Or should the server request a new token every hour, and give all users the same token for an hour?
The shell command to get a token is:
curl -X GET --user username:password \
--output token \
"https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api"
That looks like I can send an HTTP request from my JavaScript app and get back a token. Should I request the token as a file, and then have my app get the token from the file?
enter code hereWhat worked was to use Cloud Functions for Firebase (part of Google Cloud Functions), triggered on a user logging in, running Node to send an HTTP request to get the token and then write the result to an AngularJS value service.
This Cloud Function works:
// Node modules
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request'); // node module to send HTTP requests
const fs = require('fs');
admin.initializeApp(functions.config().firebase);
exports.getWatsonToken = functions.database.ref('userLoginEvent').onUpdate(event => { // authentication trigger when user logs in
var username = 'groucho',
password = 'swordfish',
url = 'https://' + username + ':' + password + '#stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api';
request({url: url}, function (error, response, body) {
var tokenService = "app.value('watsonToken','" + body + "');";
fs.writeFile('../public/javascript/services/watsonTokenValue.js', tokenService, (err) => {
if (err) throw err;
console.log('The file has been saved!');
}); // close fs.writeFile
}); // close request
}); // close getWatsonToken
In the controller:
firebase.auth().onAuthStateChanged(function(user) { // this runs on login
if (user) { // user is signed in
console.log("User signed in!");
$scope.authData = user;
firebase.database().ref('userLoginEvent').update({'user': user.uid}); // update Firebase database to trigger Cloud Function to get a new IBM Watson token
} // end if user is signed in
else { // User is signed out
console.log("User signed out.");
}
}); // end onAuthStateChanged
Walking through the Cloud Function, it injects four Node modules, including request for sending HTTP requests, and fs for writing the results to a file. Then the trigger is set for an update to a location userLoginEvent in the Firebase database (which I created from the console). Next, the HTTP request goes out. The response (the token) is called body. app.value('watsonToken','" + body + "');" is an Angular value service to wrap the token. Then fs writes all this to a location in my project.
In the AngularJS controller, onAuthStateChanged triggers when a user logins in. The user.uid is then updated to the location userLoginEvent in the Firebase database, and the Cloud Function triggers, the HTTP request goes out, and the response is written to an Angular service.
Related
Can someone help me understand how to add a permission to a MS-Graph API call?
I'm trying to create a new team/group but I'm getting a permission error. Obviously I need add the Team.Create permission if I want to create a Team. Which I did as seen in the image below
Here's the sample code where I tried to add the permission to the MSAL client request:
// Initialize Graph client
const client = graph.Client.init({
// Implement an auth provider that gets a token
// from the app's MSAL instance
authProvider: async (done) => {
try {
// Get the user's account
const account = await msalClient
.getTokenCache()
.getAccountByHomeId(userId);
let scope = process.env.OAUTH_SCOPES.split(',');
scope.push("Team.Create");
console.log("Added a extra permission request");
console.log("scope = " + scope);
if (account) {
// Attempt to get the token silently
// This method uses the token cache and
// refreshes expired tokens as needed
const response = await msalClient.acquireTokenSilent({
scopes: scope,
redirectUri: process.env.OAUTH_REDIRECT_URI,
account: account
});
console.log("\nResponse scope = " + JSON.stringify(response) + "\n");
// First param to callback is the error,
// Set to null in success case
done(null, response.accessToken);
}
} catch (err) {
console.log(JSON.stringify(err, Object.getOwnPropertyNames(err)));
done(err, null);
}
}
});
return client;
Then I get the following error:
The user or administrator has not consented to use the application with ID 'xxxxxxx'
named 'Node.js Graph Tutorial'. Send an interactive authorization request for this user and resource
I did give permissions to Team.Create in the Azure Active Directory, so how do I consent to this app gaining access? Note this code is the tutorial for learning Graph: https://learn.microsoft.com/en-us/graph/tutorials/node
Judging by the screenshot, you can't give admin consent to the permission as it is grayed out.
You'll need to try if you can grant user consent.
acquireTokenSilent won't work in this case since consent is needed.
You need to use one of the interactive authentication methods to trigger user authentication, at which time you can consent to the permission on your user's behalf.
In that sample specifically, you probably need to modify the scopes here: https://github.com/microsoftgraph/msgraph-training-nodeexpressapp/blob/08cc363e577b41dde4f6a72ad465439af20f4c3a/demo/graph-tutorial/routes/auth.js#L11.
And then trigger the /signin route in your browser.
So I'm trying to follow the security best practices and I'm sending my JWT token over my React app in a only-secure http-only cookie.
This works fine for requests but the major issue I find with this approach is, how can I tell if the user is logged-in on client-side if I can't check if the token exists? The only way I can think of is to make a simple http to a protected endpoint that just returns 200.
Any ideas? (not looking for code implementations)
The approach I would follow is to just assume the user is logged in, and make the desired request, which will send the httpOnly token automatically in the request headers.
The server side should then respond with 401 if the token is not present in the request, and you can then react in the client side accordingly.
Using an endpoint like /api/users/me
Server-side
Probably you don't only need to know if a user is already logged in but also who that user is. Therefore many APIs implement an endpoint like /api/users/me which authenticates the request via the sent cookie or authorization header (or however you've implemented your server to authenticate requests).
Then, if the request is successfully authenticated, it returns the current user. If the authentication fails, return a 401 Not Authorized (see Wikipedia for status codes).
The implementation could look like this:
// UsersController.ts
// [...]
initializeRoutes() {
this.router.get('users/me', verifyAuthorization(UserRole.User), this.getMe);
}
async getMe(req: Request, res: Response) {
// an AuthorizedRequest has the already verified JWT token added to it
const { id } = (req as AuthorizedRequest).token;
const user = await UserService.getUserById(id);
if (!user) {
throw new HttpError(404, 'user not found');
}
logger.info(`found user <${user.email}>`);
res.json(user);
}
// [...]
// AuthorizationMiddleware.ts
export function verifyAuthorization(expectedRole: UserRole) {
// the authorization middleware throws a 401 in case the JWT is invalid
return async function (req: Request, res: Response, next: NextFunction) {
const authorization = req.headers.authorization;
if (!authorization?.startsWith('Bearer ')) {
logger.error(`no authorization header found`);
throw new HttpError(401, 'unauthorized');
}
const token = authorization.split(' ')[1];
const decoded = AuthenticationService.verifyLoginToken(token);
if (!decoded) {
logger.warn(`token not verified`);
throw new HttpError(401, 'unauthorized');
}
(req as AuthorizedRequest).token = decoded;
const currentRole = UserRole[decoded.role] ?? 0;
if (currentRole < expectedRole) {
logger.warn(`user not authorized: ${UserRole[currentRole]} < ${UserRole[expectedRole]}`);
throw new HttpError(403, 'unauthorized');
}
logger.debug(`user authorized: ${UserRole[currentRole]} >= ${UserRole[expectedRole]}`);
next();
};
}
Client-side
If the response code is 200 OK and contains the user data, store this data in-memory (or as alternative in the local storage, if it doesn't include sensitive information).
If the request fails, redirect to the login page (or however you want your application to behave in that case).
Some users are getting this error back when trying to sign in using Microsoft Sign In in order to access mail via MS Graph. I've had both corporate users and personal (Hotmail.com) users both showing this error number but it works fine for most users.
This is the call:
https://login.microsoftonline.com/common/oauth2/v2.0/token
This is the error returned:
Code: InvalidAuthenticationToken
Message: CompactToken validation failed with reason code: 80049228
Any pointers? Where can I find a reference to this error number?
This means the token expired and it need to be refreshed. If you want to refresh it without user interaction you'll need a refresh_token which is returned when you obtain a token initially.
Here is how you can refresh it:
function refreshTokenIfNeeded(tokenObj){
let accessToken = oauth2.accessToken.create(tokenObj);
const EXPIRATION_WINDOW_IN_SECONDS = 300;
const { token } = accessToken;
const expirationTimeInSeconds = token.expires_at.getTime() / 1000;
const expirationWindowStart = expirationTimeInSeconds - EXPIRATION_WINDOW_IN_SECONDS;
const nowInSeconds = (new Date()).getTime() / 1000;
const shouldRefresh = nowInSeconds >= expirationWindowStart;
let promise = Promise.resolve(accessToken)
if (shouldRefresh) {
console.log("outlook365: token expired, refreshing...")
promise = accessToken.refresh()
}
return promise
}
Where tokenObj is the token object you store in your database.
Make sure it also has expires_at or otherwise oauth2.accessToken.create() will create it and calculate from the current moment in time.
More details can be found in this tutorial and in this github repo (this is where the code above was taken from)
Found a Solution To This
In my case, I was refreshing the token before using the access_token with Microsoft Graph API even once.
Once you successfully call https://login.microsoftonline.com/common/oauth2/v2.0/token You will get a refresh_token and an access_token, my guess is that you have been refreshing the token before using the first access token from the URL mentioned above.
Steps to Fix:
Call https://login.microsoftonline.com/common/oauth2/v2.0/token as you did before
Copy the access_token from the response and use it at least once with your Microsoft Graph API
Now you can copy the refresh_token (or once the access_token is expired) and exchange for a new access token
Enjoy your API integration
Smile :)
Reference:
Microsoft Authentication (Tokens) Docs - Including Refresh Token
OneDrive Refresh Token Answer
I am trying to implement my own OAuth Server with IdentityServer4, and so far everything works except the logout.
I am not using Microsoft Identity, as I already have an existing WebApp with a WebApi which is handling the user-related CRUD operations. Thus I am using an existing Database for fetching Users and validating their username and PW. If validation is successful, my validation Method returns an object of type "AuthenticatedUser" (which is a UtilityClass I made).
Edit My Client is a Xamarin App, and using IdentityModel.OidcClient2 for login. I am testing with the UWP platform, Edit which uses WebAuthenticationBroker for Login/Logout calls.
Code I use is the one from the QuickStart UI Example, with a small modification to validate the users from my existing DB:
Edit Now I am explicitly creating Claims, ClaimsIdentity, and added CookieAuthenticationDefaults.AuthenticationScheme wherever possible.
//my method for user validation
AuthenticatedUser user = await _userService.ValidateCredentials(model.Username, model.Password);
//rest of login code from quickstart ui
if (user != null)
{
await _events.RaiseAsync(new UserLoginSuccessEvent(user.FirstName, user.Id.ToString(), user.FirstName));
// only set explicit expiration here if user chooses "remember me".
// otherwise we rely upon expiration configured in cookie middleware.
AuthenticationProperties props = null;
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
//things we know about the user that we wish to store on the cookie
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.Role, user.RoleId.ToString()),
new Claim(JwtClaimTypes.Name, user.FirstName + " " + user.LastName),
new Claim(JwtClaimTypes.Subject, user.Id.ToString())
};
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
//set the cookie using the SignInAsync method
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props);
// issue authentication cookie with subject ID and username
await HttpContext.SignInAsync(user.Id.ToString(), user.FirstName, props);
//....
So far, this seems to work well. When the Login fails, I cannot access my protected Api, if the login succeeds, I get an AccessToken with the claims I requested, and I can access the protected Api methods as expected.
When I call the logout endpoint (done by a HTTP request to the endpoint, providing id_token_hint as query parameter), though, for some reason the User is not Authenticated - therefore my User is never Logged out by calling HttpContext.SignOutAsync().
if (User?.Identity.IsAuthenticated == true) //always evaluates to false?! why?
{
// delete local authentication cookie
await HttpContext.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
Edit After using WebAuthenticationBroker instead of a simple HTTP Request for calling the logout endpoint, the console Logs though state that "XamarinApp" got logged out. Even though HttpContext.SignOutAsync() was never called What does this mean? I doubt that this is Ok, but the app behaves as I want afterwards, e.g I can log in with a new user.
[16:43:12 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/endsession matched to endpoint type Endsession
[16:43:12 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Endsession, successfully created handler: IdentityServer4.Endpoints.EndSessionEndpoint
[16:43:12 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.EndSessionEndpoint for /connect/endsession
[16:43:12 Debug] IdentityServer4.Endpoints.EndSessionEndpoint
Processing signout request for anonymous
[16:43:12 Debug] IdentityServer4.Validation.EndSessionRequestValidator
Start end session request validation
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Start identity token validation
[16:43:12 Debug] IdentityServer4.EntityFramework.Stores.ClientStore
xamarinApp found in database: True
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Client found: xamarinApp / Xamarin App
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator
[16:43:12 Debug] IdentityServer4.Validation.TokenValidator
Token validation success
{
//Token details omitted here for the sake of simplicity.
}
}
[16:43:12 Information] IdentityServer4.Validation.EndSessionRequestValidator
End session request validation success
{
"ClientId": "xamarinApp",
"ClientName": "Xamarin App",
"SubjectId": "unknown",
"PostLogOutUri": "xamarinformsclients://callback",
"Raw": {
"id_token_hint": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjA3RjlGQ0VFRTVCMzM4ODkzODZCNjc2MTZCRjZCOTFEMUEwRkRBQjAiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJCX244N3VXek9JazRhMmRoYV9hNUhSb1AyckEifQ.eyJuYmYiOjE1Mjg5MDA5ODYsImV4cCI6MTUyODkwMTI4NiwiaXNzIjoiaHR0cHM6Ly9sYXB0b3AtMW0waW4zMW46NDQzODciLCJhdWQiOiJ4YW1hcmluQXBwIiwibm9uY2UiOiI4YjZjZWRkMDFhMjQ0ZDJmOWY3ZGM4NzZmM2NmZGYwNiIsImlhdCI6MTUyODkwMDk4NiwiYXRfaGFzaCI6IkZualBtd2hiZTNmOVRITjEzM0NSZWciLCJzaWQiOiJkMmJlZTgyYzg0YWY2NGI5ZDUyYmZlNmExNmU1MTNmZiIsInN1YiI6IjI4IiwiYXV0aF90aW1lIjoxNTI4OTAwOTgzLCJpZHAiOiJsb2NhbCIsInVzZXJfaWQiOiIyOCIsInJvbGVfaWQiOiI0IiwibmFtZSI6IlRpbGwgU2F1YmVybWFubiIsImZhbWlseV9uYW1lIjoiU2F1YmVybWFubiIsImFtciI6WyJwd2QiXX0.ZjwL8nuq-WD3D-pXruZtE_I5TyNNO_ZMabz2JiKVnTaTnITwGV5CIJcLcWSpBCOyaSFXKUicAtROeWLReuk_LWoUTKXcX7lyv5VP9-ItBNA13EwgsbhQX7BgS2lbE9fQU7OgGARJcpvPKaT9FabFtEZsNYW9sNeBo-6CUPkYtVH_rjRyLihFi2NlZlkHBc7_oPE0hsjf61QIwyGZEhVXvDXkP_Q9t_Bfr3_QrUF6MfyhzLs0KcMwbtlWUxYw51J8phz7RPUXbbiZ1tG9Ay4DNy8RZbzfI-uFAbrqH7waLo_f5JO15eYc-xICl22ZS_4lW0_MlzP_rq46PnGOwNBqlg",
"post_logout_redirect_uri": "xamarinformsclients://callback"
}
}
Edit As far as I can understand, this probably has to do with my Xamarin Client and Cookies. I found tutorials on how to configure a MVC Client, IDSVR4 and the Cookie Middleware, but nothing regarding native Apps, IDSVR4 and Cookie Middleware.
How is IDSVR4 (or the logout in particular) supposed to work with a non-MVC Client and IdentityModel.OidcClient?
Finally I found the reason. In the QuickstartUI Examples, the Class "AccountConteroller.cs" sets Explicit Expiration only if the user chooses the "remember me" Option. I removed the if condition, and finally the authentication cookie is properly stored, on logout my user is not null anymore, and everything is fine.
class AccountController
...
AuthenticationProperties props = null;
//ALWAYS SET EXPLICIT EXPIRATION, SO COOKIE CAN BE DELETED WHEN LOGGING OUT
//if (AccountOptions.AllowRememberLogin && model.RememberLogin)
//{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
// };
I'm using Django REST framework JWT Auth for session creation and permissions, the only problem is: when I log in and after the token expires I can't continue doing the operation I want, unless I log in again. And I didn't fully understand the documentations provided for the additional settings.
So can any one explain a method for dynamically creating (and refreshing) my token (following best practices) so that I can keep doing operations when I'm logged in.
P.S: I'm using angular 2 for my front end, and I'm inserting the token in the Http requests headers. Thanks.
JWT token refresh is a little confusing, and i hope this explanation helps.
tokens have an issued at time (iat in the token)
tokens have an expiration date (now() + 1 hour, for example)
the token can't be changed. server can only issue a new one
iat never changes, but expires does change with each refresh
When you want to extend a token, this is what happens:
You send your token to the server endpoint /.../refresh/
Server checks its not expired: now() <= token.iat + JWT_REFRESH_EXPIRATION_DELTA
If not expired:
Issue a NEW token (returned in the json body, same as login)
New Token is valid for now() + JWT_EXPIRATION_DELTA
The issued at value in the token does not change
App now has 2 tokens (technically).
App discards the old token and starts sending the new one
If expired: return error message and 400 status
Example
You have EXPIRATION=1 hour, and a REFRESH_DELTA=2 days. When you login you get a token that says "created-at: Jun-02-6pm". You can refresh this token (or any created from it by refreshing) for 2 days. This means, for this login, the longest you can use a token without re-logging-in, is 2 days and 1 hour. You could refresh it every 1 second, but after 2 days exactly the server would stop allowing the refresh, leaving you with a final token valid for 1 hour. (head hurts).
Settings
You have to enable this feature in the backend in the JWT_AUTH settings in your django settings file. I believe that it is off by default. Here are the settings I use:
JWT_AUTH = {
# how long the original token is valid for
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=2),
# allow refreshing of tokens
'JWT_ALLOW_REFRESH': True,
# this is the maximum time AFTER the token was issued that
# it can be refreshed. exprired tokens can't be refreshed.
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
Then you can call the JWT refresh view, passing in your token in the body (as json) and getting back a new token. Details are in the docs at http://getblimp.github.io/django-rest-framework-jwt/#refresh-token
$ http post localhost:8000/auth/jwt/refresh/ --json token=$TOKEN
Which returns:
HTTP 200
{
"token": "new jwt token value"
}
I've had same problem in angularjs and I've solved it by writing a custom interceptor service for my authentication headers.
Here's my code:
function($http, $q, store, jwtHelper) {
let cache = {};
return {
getHeader() {
if (cache.access_token && !jwtHelper.isTokenExpired(cache.access_token)) {
return $q.when({ 'Authorization': 'Token ' + cache.access_token });
} else {
cache.access_token = store.get('token');
if (cache.access_token && !jwtHelper.isTokenExpired(cache.access_token)) {
return $q.when({ 'Authorization': 'Token ' + cache.access_token });
} else {
return $http.post(localhost + 'api-token-refresh/',{'token': cache.access_token})
.then(response => {
store.set('token', response.data.token);
cache.access_token = response.data.token;
console.log('access_token', cache.access_token);
return {'Authorization': 'Token ' + cache.access_token};
},
err => {
console.log('Error Refreshing token ',err);
}
);
}
}
}
};
}
Here, on every request I've had to send, the function checks whether the token is expired or not.
If its expired, then a post request is sent to the "api-token-refresh" in order to retrieve the new refreshed token, prior to the current request.
If not, the nothing's changed.
But, you have to explicitly call the function getHeader() prior to the request to avoid circular dependency problem.
This chain of requests can be written into a function like this,
someResource() {
return someService.getHeader().then(authHeader => {
return $http.get(someUrl, {headers: authHeader});
});
}
just add this line to your JWT_AUTH in settings.py file:
'JWT_VERIFY_EXPIRATION': False,
it worked for me.