That looks a little complex. In contrast to the above example, the default $System.Encryption.GenCryptRand() size appears to be 8 as $L(user.Salt) resolves to 8. After a bit of experimentation, I found I didn't need to encode anything at all. In this example, I'm using user test1 with password P@ssw0rd on a non-unicode 8-bit Cache installation. Use $SYSTEM.Version.IsUnicode() to check your installation

Do ##class(Security.Users).Exists("test1",.user,.status1)
set storedHash = user.Password 
set computed=$System.Encryption.PBKDF2("P@ssw0rd", 1024, user.Salt,20,160)

Produces the following 20 byte hash using PBKDF2 with 1024 iterations, 64 bits of salt and SHA1 (160)

%SYS>zw storedHash
storedHash="n"_$c(138)_"z iSWWs"_$c(11)_"cbM"_$c(27)_"nY'"_$c(3,152)_"H"

%SYS>zw computed
computed="n"_$c(138)_"z iSWWs"_$c(11)_"cbM"_$c(27)_"nY'"_$c(3,152)_"H"

I'm simply creating an interface between Caché and a web app that is using the .NET Core Identity Model. The PasswordSignInAsync() method returns a false sign-in result and I'm trying to determine why the password isn't being accepted. 

Thanks @Barton Pravin for clarifying scope and providing the code snippets!  If you want the claim to be part of the Token endpoint response message, you can use Do properties.ResponseProperties.SetAt(roles,"roles") 

Heres's a sample of ValidateUser customization

// Use the Cache roles for the user to setup a custom property.
Set sc=##class(Security.Roles).RecurseRoleSet(prop("Roles"),.roles)
If $$$ISERR(sc) Quit 0

Set roles=prop("Roles")
Do properties.CustomProperties.SetAt(roles,"roles") 

Set tClaim = ##class(%OAuth2.Server.Claim).%New()
Do properties.ResponseProperties.SetAt(roles,"roles")
Do properties.IntrospectionClaims.SetAt(tClaim,"roles")
Do properties.UserinfoClaims.SetAt(tClaim,"roles")
Do properties.JWTClaims.SetAt(tClaim,"roles") 
Do properties.SetClaimValue("roles",roles)

And a sample of the REST API application

Class API.DemoBearerToken Extends %CSP.REST
Parameter APIHOST = "localhost";

Parameter APIPORT = 57773;

Parameter APIPATH = "/api/demobearertoken";

Parameter CHARSET = "utf-8";

Parameter CONTENTTYPE = "application/json";

Parameter OAUTH2CLIENTREDIRECTURI = "https://localhost:57773/api/demobearertoken/example";

Parameter OAUTH2APPNAME = "demobearertoken";

XData UrlMap [ XMLNamespace = "" ]
<Route Url="/getToken" Method="Get" Call="GetToken"/>

Classmethod AccessCheck(Output pAuthorized As %Boolean = 0) as %Status

Set dayNum = $p($H,",",1)
Set timeNum = $p($H,",",2)

Set accessToken = ..GetAccessTokenFromRequest(%request) 
Set scope = "createModify openid profile publish" 
Set isValidToken=##class(%SYS.OAuth2.Validation).ValidateJWT(..#OAUTH2APPNAME,.accessToken,,,.jsonValidationObject,.securityParameters,.error)
Set ^LOG(dayNum,timeNum,$UserName,"API.DemoBearerToken",$ztimestamp,"AccessCheck")=$zdatetime(dayNum_","_timeNum,4,1,,,4)_"*"_isValidToken

Set:isValidToken=1 pAuthorized=1
Set:isValidToken=0 pAuthorized=0 
Quit $$$OK
ClassMethod GetToken() As %Status
#dim %response as %CSP.Response
Set %response.Expires = 86400
Set %response.Headers("Cache-Control") = "max-age=86400"

Set dayNum = $p($H,",",1)
Set timeNum = $p($H,",",2) 
Set ^LOG(dayNum,timeNum,$UserName,"API.DemoBearerToken",$ztimestamp,"GetToken")=$zdatetime(dayNum_","_timeNum,4,1,,,4) 
Set accessToken = ..GetAccessTokenFromRequest(%request)
Set scope = "createModify openid profile publish" 
Set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(..#OAUTH2APPNAME,.accessToken,,,.jsonValidationObject,.securityParameters,.error) 

Set introspectionStatus=##class(%SYS.OAuth2.AccessToken).GetIntrospection(..#OAUTH2APPNAME,accessToken,.introspectionJSON) 
Set userInfoStatus = ##class(%SYS.OAuth2.AccessToken).GetUserinfo(..#OAUTH2APPNAME,accessToken,,.userInfoJSON) 
Set jsonResponse = {}.%Set("OAUTH2APPNAME",..#OAUTH2APPNAME)
Do jsonResponse.%Set("ValidateJWT",valid) 

Do jsonResponse.%Set("jsonValidationObject",jsonValidationObject)
Do jsonResponse.%Set("IntrospectionJSON",introspectionJSON)
Do jsonResponse.%Set("sc_userinfo",$$$ISOK(userInfoStatus)) 

If $$$ISOK(userInfoStatus) {
 Do jsonResponse.%Set("UserInfoJSON",userInfoJSON)

Write jsonResponse.%ToJSON()

Quit $$$OK 


And the Postman response for GET https://{{SERVER}}:{{SSLPORT}}/api/demobearertoken/getToken


"OAUTH2APPNAME": "demobearertoken",

"ValidateJWT": 1,

"jsonValidationObject": {

"jti": "https://localhost:57773/oauth2.mxn0URwYVkmaX9BSKHGIzISi-cI",

"iss": "https://localhost:57773/oauth2",

"sub": "test1",

"exp": 1565708268,

"aud": "xxxxxxxxxxxxxxxxxxxxx",

"roles": "%DB_CODE,%Manager,createModify,publish"


"IntrospectionJSON": {

"active": true,

"scope": "createModify openid profile publish",

"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",

"username": "test1",

"token_type": "bearer",

"exp": 1565708268,

"sub": "test1",

"aud": "xxxxxxxxxxxxxxxxxxxxxxx",

"iss": "https://localhost:57773/oauth2",

"roles": "%DB_CODE,%Manager,createModify,publish"


"sc_userinfo": 1,

"UserInfoJSON": {

"sub": "test1",

"roles": "%DB_CODE,%Manager,createModify,publish"


You can see 'roles' has been added to JWT, Introspection and UserInfo claim types. In my real-world application it's probably sufficient to add it to the JWT. 

Also @Eduard Lebedyuk  suggested using the AccessCheck method to verify the token in the post From Cache how to Retrieve and Use/Reuse a Bearer Token to authenticate and send data to REST web service?  AccessCheck is called before anything else so if the token is invalid or has expired a 401 Unauthorized HTTP Response is returned. A web application consuming this REST API can then process this unauthorized status and return them to the login screen. 

I can also see in the classmethod ##class(OAuth2.Server.Token).ReturnToken(client,token) where there is a section on adding customized response properties but where are these set?

My AccessToken.ResponseProperties array appears to be empty

Set json.scope=token.Scope
// Add the customized response properties
Set key=""
For {
Set value=token.Properties.ResponseProperties.GetNext(.key)
If key="" Quit
Set $property(json,key)=value

You could use or add the document to the GitHub repo.

There is a way to post documents on Intersystems Community under Edit Post -> Change Additional Settings, which I documented here but it's not user friendly and I didn't automatically see links to attached documents within the post so I had to manually add the links. Community feedback suggests they may turn this feature off at some point so I'd recommend any of the above options instead.

I edited this post to upload a PDF of the Global Summit 2019 Agenda but I couldn't find a link to the file I uploaded any where in this post - just a blue bar saying "Attached documents". I obtained the following link by going into the edit post view and mousing over the attached file

Global Summit 2019 Grid.pdf

Thanks for sharing this.  I would be interested in seeing some of the data structures you decided on with dummy data.

I would typically use a percent (%) global if I wanted a global to be accessible across multiple namespaces but I don't usually mix percent globals and class storage definitions.

You could also try a tool like Postman to test service calls and authentication methods.  If you tick the Password checkbox it enables Basic authentication (plain text username/password) is enabled. You can  also use bearer tokens instead, which is a popular authentication scheme.

While authentication/authorization isn't really covered in great detail, REST and Relaxation is a good starting point for REST development and it comes with a video and source code.

You should also double-check your URL is correct and resource permissions are correct. You probably only need permissions on the ENSEMBLE namespace and there might be a resource that defines this. Your URL is probably something like http://yourserver/rest/coffeemakerapp/coffeemaker