Authentication/authorization in the REST API

Hi!

I’m working on REST API with authentication/authorization.
It looks like I have to choose between controlling endpoint and controlling signing key:

New-UDAuthenticationMethod [-Audience <string>] [-Issuer <string>] [-SigningKey <string>] [<CommonParameters>]
New-UDAuthenticationMethod [-Endpoint <scriptblock>] [<CommonParameters>]

For now I plan to workaround it with two APIs: one that uses authentication and provides default token and endpoint to request another token (generated using Grant-UDJsonWebToken) with more control:

$auth = New-UDAuthenticationMethod -Endpoint {
    param([PSCredential]$Credential)
    $userName = $Credential.UserName
    $role = if ($userName -eq 'Bartek') {
        'admin'
    } else {
        'luser'
    }
    New-UDAuthenticationResult -Success -UserName $userName -Role $role -ErrorMessage ''
}

Start-UDRestApi -Port 1001 -Endpoint @(
    New-UDEndpoint -Url user/me -Method GET -Endpoint {
        @{
            User = $User
            Token = Grant-UDJsonWebToken -Identity $User -SigningKey abc12345abc12345
        } | ConvertTo-JsonEx
    }
) -AuthenticationMethod $auth

And endpoint that will be used for actual work, that uses token signed with a key (obviously not the one shared):

$auth2 = New-UDAuthenticationMethod -SigningKey abc12345abc12345
Start-UDRestApi -Port 1002 -Endpoint @(
    New-UDEndpoint -Url test -Method GET -Endpoint {
        @{
            User = $User
        } | ConvertTo-JsonEx
    }
) -AuthenticationMethod $auth2

I will probably also limit lifetime of the 2nd token. Now the question is: how can I achieve it without this workaround? And question that seems as important: how can I notify requester how long will first token be valid (so that it’s obvious when you have to request new one)? It’s possible with latter (I control output) but former seems to always return similar object, with token added by UD itself (so even if I assign value to a token using Grant-UDJsonWebToken command, that token is replaced by a different one - or so it seems looking at the expiry date on the resultant token).

The -AuthenticationMethod parameter is an array. So you should be able to do this.

Import-Module UniversalDashboard

$auth = New-UDAuthenticationMethod -Endpoint {
    param([PSCredential]$Credential)
    $userName = $Credential.UserName
    $role = if ($userName -eq 'Bartek') {
        'admin'
    } else {
        'luser'
    }
    New-UDAuthenticationResult -Success -UserName $userName -Role $role -ErrorMessage ''
}

$auth2 = New-UDAuthenticationMethod -SigningKey abc12345abc12345

Start-UDRestApi -Port 1001 -Endpoint @(

    New-UDEndpoint -Url test -Method GET -Endpoint {
        @{
            User = $User
        } | ConvertTo-JsonEx
    }

    New-UDEndpoint -Url user/me -Method GET -Endpoint {
        @{
            User = $User
            Token = Grant-UDJsonWebToken -Identity $User -SigningKey abc12345abc12345 
        } | ConvertTo-JsonEx
    }
) -AuthenticationMethod @($auth, $auth2)

$Token = Invoke-RestMethod -Uri http://localhost:1001/api/login -Method POST -Body @{ UserName = "Bartek"; Password = "Test" } 

Invoke-RestMethod -Uri http://localhost:1001/api/user/me -Headers @{ Authorization = "Bearer $($Token.Token)" }

You can use the JwtSecurityTokenHandler class to parse a token.

PS C:\Users\adamr> $jwtHelper = [System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler]::new()
PS C:\Users\adamr> $jwtHelper.ReadJwtToken($token.token)


Actor                   :
Audiences               : {UniversalDashboard}
Claims                  : {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: Bartek,
                          http://schemas.xmlsoap.org/ws/2005/05/identity/claims/hash:
                          01cc2c8a-198a-4f6a-97b2-74b45a94ed89, sub: UniversalDashboard,
                          http://schemas.microsoft.com/ws/2008/06/identity/claims/role: admin...}
EncodedHeader           : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
EncodedPayload          : eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiQmFydGVrI
                          iwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvaGFzaCI6IjAxY2MyYz
                          hhLTE5OGEtNGY2YS05N2IyLTc0YjQ1YTk0ZWQ4OSIsInN1YiI6IlVuaXZlcnNhbERhc2hib2FyZCIsImh0dHA6Ly9zY2h
                          lbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6ImFkbWluIiwibmJmIjoxNTQ0
                          MDA2NTg3LCJleHAiOjE1NDQwMDgwODcsImlzcyI6InBvc2h1ZC5jb20iLCJhdWQiOiJVbml2ZXJzYWxEYXNoYm9hcmQif
                          Q
Header                  : {[alg, HS256], [typ, JWT]}
Id                      :
Issuer                  : poshud.com
Payload                 : {[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, Bartek],
                          [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/hash,
                          01cc2c8a-198a-4f6a-97b2-74b45a94ed89], [sub, UniversalDashboard],
                          [http://schemas.microsoft.com/ws/2008/06/identity/claims/role, admin]...}
InnerToken              :
RawAuthenticationTag    :
RawCiphertext           :
RawData                 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lk
                          ZW50aXR5L2NsYWltcy9uYW1lIjoiQmFydGVrIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZ
                          GVudGl0eS9jbGFpbXMvaGFzaCI6IjAxY2MyYzhhLTE5OGEtNGY2YS05N2IyLTc0YjQ1YTk0ZWQ4OSIsInN1YiI6IlVuaX
                          ZlcnNhbERhc2hib2FyZCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGF
                          pbXMvcm9sZSI6ImFkbWluIiwibmJmIjoxNTQ0MDA2NTg3LCJleHAiOjE1NDQwMDgwODcsImlzcyI6InBvc2h1ZC5jb20i
                          LCJhdWQiOiJVbml2ZXJzYWxEYXNoYm9hcmQifQ.EzWHWFao6BJjCTp3i-UsTvSdLDl3tdG_4Ae7-PIH2lI
RawEncryptedKey         :
RawInitializationVector :
RawHeader               : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
RawPayload              : eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiQmFydGVrI
                          iwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvaGFzaCI6IjAxY2MyYz
                          hhLTE5OGEtNGY2YS05N2IyLTc0YjQ1YTk0ZWQ4OSIsInN1YiI6IlVuaXZlcnNhbERhc2hib2FyZCIsImh0dHA6Ly9zY2h
                          lbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6ImFkbWluIiwibmJmIjoxNTQ0
                          MDA2NTg3LCJleHAiOjE1NDQwMDgwODcsImlzcyI6InBvc2h1ZC5jb20iLCJhdWQiOiJVbml2ZXJzYWxEYXNoYm9hcmQif
                          Q
RawSignature            : EzWHWFao6BJjCTp3i-UsTvSdLDl3tdG_4Ae7-PIH2lI
SecurityKey             :
SignatureAlgorithm      : HS256
SigningCredentials      :
EncryptingCredentials   :
SigningKey              :
Subject                 : UniversalDashboard
ValidFrom               : 12/5/2018 10:43:07 AM
ValidTo                 : 12/5/2018 11:08:07 AM

It does look like there is an issue with the default expiration time. The docs state a year but it looks more like 25 minutes.

Thanks Adam, I knew I was missing something obvious here… :slight_smile:

For token parsing: could you please consider adding cmdlet for that? That was my first instinct (Get-Command *JsonWebToken*) and I think it will be pretty common for PowerShell users to expect it to be there… :slight_smile:

Sure! I opened an issue here: https://github.com/ironmansoftware/universal-dashboard/issues/508

If you have any opinions on name, feel free to throw them at that issue.